enigma-memory 0.1.5 → 0.1.7

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 CHANGED
@@ -1,48 +1,62 @@
1
1
  # Enigma
2
2
 
3
- Enigma is a provider-agnostic AI memory custody and proof layer. It gives a user or enterprise a local canonical memory vault, emits offline-verifiable receipts for Enigma-controlled lifecycle events, and connects that vault to assistants through CLI, MCP, browser, desktop, relay, gateway, and enterprise policy surfaces.
3
+ Enigma Memory is a local-first AI Memory Passport. It gives you one canonical local memory vault, receipt-backed proof for Enigma-controlled events, and MCP connections into assistants such as Claude Desktop, Cursor, Kimi Code, and generic MCP clients.
4
4
 
5
- Current status:
5
+ Start with the package path below. You do not need to understand relay, gateway, mesh, browser, desktop, or hosted infrastructure before trying Enigma locally.
6
6
 
7
- - Local production foundation: CLI, verifier, vault, passport, boundary, MCP server, connector, importer, relay, gateway, enterprise, mesh, browser-extension, and desktop scaffold code exist in this repository.
8
- - Published npm package: package bins and module entry points are available as `enigma-memory`; use the npm install path below for the simplest onboarding flow.
9
- - Source-only artifacts: `docs/`, `Dockerfile`, and `docker-compose.yml` live in the source checkout. The package README and CLI help are the package-included install guides; the full runbooks require the repository or hosted docs.
10
- - Hosted cloud is not included by default. Hosted relay/gateway/cloud operation requires deployment credentials, a domain, TLS, production durable storage, KMS/secrets, monitoring, backups, operator policy, and a completed operator acceptance packet. Local relay/gateway `--state-file` demo state does not satisfy those hosted/BYOC requirements.
11
- - Cloudflare API/domain/hosting automation is documented but safe-by-default: [`docs/cloudflare-token-and-domain-runbook.md`](docs/cloudflare-token-and-domain-runbook.md) gives the token recipe, Registrar prerequisites, local token storage rule, search/check flow, explicit domain+price purchase gate, Pages deploy gate, custom-domain steps, and post-setup token rotation.
12
- - Overnight execution plan: [`docs/overnight-build-master-plan.md`](docs/overnight-build-master-plan.md) defines the GPT-5.5/Kimi overnight build cadence, acceptance gates, exact non-claims, and hosted/BYOC blockers.
13
- - Security and production review artifacts: [`SECURITY.md`](SECURITY.md) defines reporting, safe harbor, disclosure, incident, secret-handling, plaintext-minimization, and proof-boundary policy; [`docs/security-threat-model.md`](docs/security-threat-model.md) maps assets, trust boundaries, controls, residual risks, and verification evidence; [`docs/operator-acceptance-packet.md`](docs/operator-acceptance-packet.md) is required before hosted/BYOC can be called live.
14
- - Public API reference: [`docs/public-api-reference.md`](docs/public-api-reference.md) lists package exports, CLI bins, MCP tools/resources/prompts, relay/gateway endpoints, importer/capsule APIs, connector profiles, verifier outputs, schemas, and local-vs-hosted boundaries.
15
- - Local release provenance/SBOM: [`docs/release-provenance-and-sbom.md`](docs/release-provenance-and-sbom.md) documents `npm run provenance:local -- --out ./.enigma/release-provenance.json` as unsigned local package-surface inventory and SHA-256 evidence only, not signed attestation, registry provenance, source-control proof, SLSA/compliance, Docker image, or hosted/cloud deployment evidence.
16
- - Reviewer packet: [`docs/reviewer-packet.md`](docs/reviewer-packet.md) documents `npm run review:packet -- --out ./.enigma-review-packet --public-site <path-to-_public_site>` as a local hand-review bundle for package, release-audit, provenance, and optional generated public-site evidence; it is not npm publication, live Cloudflare deployment, Docker runtime proof, hosted/BYOC readiness, legal approval, signed provenance, or compliance evidence.
7
+ The package path needs no database, provider credential, cloud credential, npm publishing token, or external account. Hosted cloud and BYOC operation are separate advanced modes that require real deployment credentials, domains, TLS, durable storage, KMS/secrets, monitoring, backups, and operator acceptance.
17
8
 
18
9
  Enigma does not claim that a closed provider deleted internal data, that model weights forgot, or that provider-native memory disappeared. It proves facts about Enigma-controlled vault state, receipts, checkpoints, and declared boundary operations.
19
10
 
20
- ## Install and run locally
11
+ ## Install once, use everywhere
21
12
 
22
13
  Prerequisites:
23
14
 
24
15
  - Node.js `>=24`
25
- - No database, package registry account, provider credential, or cloud credential for the local package quickstart
16
+ - No database, package registry account, provider credential, or cloud credential for the local setup path
26
17
  - Git only when you choose the advanced source-checkout path
27
18
 
28
- ## Quickstart from npm
29
-
30
- Use the published package first:
19
+ Use Enigma as a one-time AI Memory Passport setup: install the package, create the local Enigma workspace once, then use the same memory/search/context/verify/connect loop from any supported client.
31
20
 
32
21
  ```sh
33
22
  npm install -g enigma-memory
34
- enigma quickstart --bundle ./.enigma/bundle.json --overwrite
35
- enigma doctor
36
- enigma-relay demo
37
- enigma-gateway demo
23
+ enigma setup --overwrite
24
+ ```
25
+
26
+ `enigma setup --overwrite` is the safe default. It writes local Enigma artifacts under the workspace `.enigma` path and emits deterministic, public-safe JSON without printing raw memory plaintext. It does not write Claude, Cursor, Kimi, or other third-party app configs.
27
+
28
+ To let setup detect installed or already-configured clients and show the connector plan without mutating client configs:
29
+
30
+ ```sh
31
+ enigma setup --client auto --overwrite
32
+ ```
33
+
34
+ `--client auto` selects clients found by connector detection and falls back to the default setup client list when none are present. The setup output lists which clients were selected, which were skipped, and why.
35
+
36
+ When you are ready to explicitly write connector entries for installed/config-present clients only:
37
+
38
+ ```sh
39
+ enigma setup --connect-installed --overwrite
40
+ ```
41
+
42
+ `--connect-installed` implies auto client selection and is an explicit client-config write flag. It skips missing client configs instead of creating every default client config. Only explicit write flags mutate client configs. Existing explicit connector writes remain available through `enigma connect <client>` without `--dry-run`, and existing `enigma setup --write-connectors` behavior for explicit/default setup clients is unchanged; keep `--dry-run` while reviewing a single planned MCP entry.
43
+
44
+ After setup, use the same local vault from the CLI or connected clients:
45
+
46
+ ```sh
47
+ enigma remember --text-file ./memory.txt
48
+ enigma search --query "..."
49
+ enigma context --query "..." --optimize
50
+ enigma verify --export ./.enigma/export.json
51
+ enigma connect claude-desktop --dry-run
38
52
  ```
39
53
 
40
- `enigma quickstart` creates a local Enigma workspace for proof review: a local vault bundle, a context pack, an export proof bundle, and a verify report. These artifacts prove Enigma-controlled local vault state, receipts, checkpoints, and verification results only; they do not prove provider deletion, provider model forgetting, provider-native memory removal, hosted availability, or compliance certification.
54
+ The local Enigma vault remains canonical. Provider-native memory is non-canonical and should be treated as a convenience cache only. Enigma receipts prove Enigma-controlled vault state, receipts, checkpoints, and declared boundary operations; they do not prove provider deletion, provider model forgetting, provider-native memory removal, hosted availability, ROI/savings, or compliance certification.
41
55
 
42
56
  One-off execution without a global install:
43
57
 
44
58
  ```sh
45
- npx --yes --package enigma-memory enigma quickstart --bundle ./.enigma/bundle.json --overwrite
59
+ npx --yes --package enigma-memory enigma setup --overwrite
46
60
  ```
47
61
 
48
62
  ## Advanced/source-only path
@@ -60,7 +74,7 @@ enigma-gateway demo
60
74
 
61
75
  `install:local` is dry-run unless `--execute` is present. The command above installs the checked-out package globally and creates a local vault bundle. It does not require Cloudflare, OpenAI, Anthropic, npm publish credentials, a database, or hosted infrastructure.
62
76
 
63
- Manual alternative: create a no-network local vault, write one local memory from a file, compile a context pack, export a proof bundle, and verify it. Use a tenant-approved smoke file; do not expand private memory into shell argv.
77
+ Manual alternative: inspect the lower-level local vault steps that `enigma setup` prepares for you. Use a tenant-approved smoke file; do not expand private memory into shell argv.
64
78
 
65
79
  POSIX shell:
66
80
 
@@ -72,7 +86,7 @@ enigma init --bundle ./.enigma/bundle.json --subject local-user --display-name "
72
86
  enigma remember --bundle ./.enigma/bundle.json --text-file "$ENIGMA_DEMO_MEMORY_FILE" --purpose user_memory --tags local
73
87
  enigma context --bundle ./.enigma/bundle.json --query "local context" --purpose local_answer --out ./.enigma/context-pack.json
74
88
  enigma export --bundle ./.enigma/bundle.json --out ./.enigma/export.json
75
- enigma verify --bundle ./.enigma/export.json
89
+ enigma verify --export ./.enigma/export.json
76
90
  ```
77
91
 
78
92
  Windows PowerShell:
@@ -85,7 +99,7 @@ enigma init --bundle .\.enigma\bundle.json --subject local-user --display-name "
85
99
  enigma remember --bundle .\.enigma\bundle.json --text-file $env:ENIGMA_DEMO_MEMORY_FILE --purpose user_memory --tags local
86
100
  enigma context --bundle .\.enigma\bundle.json --query "local context" --purpose local_answer --out .\.enigma\context-pack.json
87
101
  enigma export --bundle .\.enigma\bundle.json --out .\.enigma\export.json
88
- enigma verify --bundle .\.enigma\export.json
102
+ enigma verify --export .\.enigma\export.json
89
103
  ```
90
104
 
91
105
  The bundle is local. Exported proof artifacts contain encrypted/committed vault state and receipt metadata; do not paste raw memory plaintext into relay records, witness checkpoints, SIEM events, public proof artifacts, or shell command lines.
@@ -150,16 +164,33 @@ Supported connector profiles are:
150
164
  - `opencode`
151
165
  - `generic-mcp`
152
166
 
153
- Use the config in `docs/client-connectors.md` from a source checkout for Claude Desktop, Cursor, Kimi Code, VS Code/Cline, Roo Code, OpenCode, or any MCP-compatible client. The generated entry defaults to command `enigma-mcp` and sets `ENIGMA_BUNDLE` to the local vault bundle.
154
-
155
- CLI connector commands:
167
+ After `enigma setup --overwrite`, preview a client connection before writing anything:
156
168
 
157
169
  ```sh
158
- enigma doctor
159
- enigma install --bundle "$HOME/.enigma/bundle.json"
160
- enigma connect claude-desktop --bundle "$HOME/.enigma/bundle.json"
161
- enigma connect kimi-code --bundle "$HOME/.enigma/bundle.json" --mcp-command "/absolute/path/to/enigma-mcp"
162
- enigma disconnect claude-desktop
170
+ enigma connect claude-desktop --dry-run
171
+ enigma connect cursor --dry-run
172
+ enigma connect kimi-code --dry-run
173
+ enigma connect generic-mcp --dry-run
174
+ ```
175
+
176
+ For the one-time setup flow, `enigma setup --client auto --overwrite` reports the installed/config-present clients that connector detection selected and the clients it skipped with reasons. It remains read-only for client configs. `enigma setup --connect-installed --overwrite` is the explicit setup-time write path for installed/config-present clients only; missing configs are skipped rather than created.
177
+
178
+ Remove `--dry-run` from `enigma connect <client>` only after you are ready for Enigma to merge the `mcpServers.enigma` entry into that specific client config. The safe default setup command never writes third-party app configs.
179
+
180
+ Manual MCP entry for Claude Desktop, Cursor, Kimi Code, or any generic MCP client:
181
+
182
+ ```json
183
+ {
184
+ "mcpServers": {
185
+ "enigma": {
186
+ "command": "enigma-mcp",
187
+ "args": [],
188
+ "env": {
189
+ "ENIGMA_BUNDLE": "/absolute/path/to/.enigma/bundle.json"
190
+ }
191
+ }
192
+ }
193
+ }
163
194
  ```
164
195
 
165
196
  Use `--mcp-command` (alias `--command`) when a GUI app cannot find shell-installed binaries or needs a `.cmd` path on Windows.
@@ -291,7 +322,7 @@ test -f "$ENIGMA_DEMO_MEMORY_FILE"
291
322
  enigma init --bundle ./.enigma/bundle.json
292
323
  enigma remember --bundle ./.enigma/bundle.json --text-file "$ENIGMA_DEMO_MEMORY_FILE" --purpose local_test
293
324
  enigma export --bundle ./.enigma/bundle.json --out ./.enigma/export.json
294
- enigma verify --bundle ./.enigma/export.json
325
+ enigma verify --export ./.enigma/export.json
295
326
  enigma boundary run --scenario committed_crossing
296
327
  ```
297
328
 
@@ -11,13 +11,17 @@ import { runBoundarySimulation } from '../../../packages/boundary/src/index.js';
11
11
  import { startStdioServer } from '../../../packages/mcp-server/src/index.js';
12
12
  import { runMeshDemo } from '../../../packages/mesh/src/index.js';
13
13
  import { runEnterpriseDemo } from '../../../packages/enterprise/src/index.js';
14
- import { connectClient, disconnectClient, doctorConnectors, getClientProfile, renderMcpConfig, supportedClients } from '../../../packages/connectors/src/index.js';
14
+ import { connectClient, disconnectClient, doctorConnectors, getClientProfile, planConnectWizard, renderMcpConfig, supportedClients } from '../../../packages/connectors/src/index.js';
15
15
  import { exportEnigmaCapsule, importChatGptExport, importClaudeMemory, importEnigmaCapsule, importLangGraphStore, importLettaAgentFile, importMem0Export, importZepGraphitiExport } from '../../../packages/importers/src/index.js';
16
16
  import * as relayServer from '../../relay/src/server.mjs';
17
17
  import * as gatewayServer from '../../gateway/src/server.mjs';
18
18
  import { verifyBundle } from '../../verifier/bin/enigma-verify.mjs';
19
19
  import { createNativeHostInstallPlan, createNativeHostManifest } from '../../native-host/bin/enigma-native-host.mjs';
20
20
  import { aggregateUsageEvents, createUsageEvent } from '../../../packages/metering/src/index.js';
21
+ import {
22
+ createMemoryAccessReceipt,
23
+ createMemoryOptimizationPlan,
24
+ } from '../../../packages/optimizer/src/index.js';
21
25
  import {
22
26
  createConsumerGpuCapacityProfile,
23
27
  createOperatorServiceQuote,
@@ -33,6 +37,21 @@ export const DEFAULT_GATEWAY_PORT = 8797;
33
37
  const DEFAULT_QUICKSTART_MEMORY = 'Enigma quickstart demo memory: local proof bundles can be created and verified without provider or cloud credentials.';
34
38
  const DEFAULT_CROSS_MODEL_DEMO_BUNDLE = '.enigma/cross-model-demo-bundle.json';
35
39
  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.';
40
+ const DEFAULT_SETUP_CLIENTS = Object.freeze(['generic-mcp', 'claude-desktop', 'cursor', 'kimi-code']);
41
+ const SETUP_CLAIM_BOUNDARIES = Object.freeze({
42
+ local_only: true,
43
+ provider_credentials_required: false,
44
+ provider_native_memory_canonical: false,
45
+ provider_deletion_proof: false,
46
+ model_forgetting_proof: false,
47
+ roi_or_savings_guarantee: false,
48
+ compliance_certification: false,
49
+ });
50
+ const QUICKSTART_ARTIFACT_NAMES = Object.freeze({
51
+ contextPack: 'context-pack.json',
52
+ export: 'export.json',
53
+ verifyReport: 'verify-report.json',
54
+ });
36
55
  const CROSS_MODEL_PROFILES = Object.freeze([
37
56
  { id: 'chatgpt', provider: 'chatgpt', model: 'chatgpt-mcp-profile', label: 'ChatGPT' },
38
57
  { id: 'claude', provider: 'claude', model: 'claude-mcp-profile', label: 'Claude' },
@@ -77,6 +96,19 @@ const IMPORTERS = Object.freeze({
77
96
 
78
97
  export const REQUIRED_PACKAGE_BINS = Object.freeze(['enigma', 'enigma-verify', 'enigma-mcp', 'enigma-relay', 'enigma-gateway', 'enigma-native-host']);
79
98
 
99
+ function setFlagValue(flags, name, value) {
100
+ if (!flags.has(name)) {
101
+ flags.set(name, value);
102
+ return;
103
+ }
104
+ const current = flags.get(name);
105
+ if (Array.isArray(current)) {
106
+ current.push(value);
107
+ } else {
108
+ flags.set(name, [current, value]);
109
+ }
110
+ }
111
+
80
112
  function parseArgs(argv) {
81
113
  const flags = new Map();
82
114
  for (let i = 0; i < argv.length; i += 1) {
@@ -84,11 +116,11 @@ function parseArgs(argv) {
84
116
  if (!arg.startsWith('--')) continue;
85
117
  const eq = arg.indexOf('=');
86
118
  if (eq !== -1) {
87
- flags.set(arg.slice(2, eq), arg.slice(eq + 1));
119
+ setFlagValue(flags, arg.slice(2, eq), arg.slice(eq + 1));
88
120
  } else if (!argv[i + 1] || argv[i + 1].startsWith('--')) {
89
- flags.set(arg.slice(2), true);
121
+ setFlagValue(flags, arg.slice(2), true);
90
122
  } else {
91
- flags.set(arg.slice(2), argv[i + 1]);
123
+ setFlagValue(flags, arg.slice(2), argv[i + 1]);
92
124
  i += 1;
93
125
  }
94
126
  }
@@ -100,6 +132,19 @@ function getFlag(flags, names, fallback = undefined) {
100
132
  return fallback;
101
133
  }
102
134
 
135
+ function lastFlagValue(value) {
136
+ return Array.isArray(value) ? value[value.length - 1] : value;
137
+ }
138
+
139
+ function booleanFlag(flags, names, fallback = false) {
140
+ const value = lastFlagValue(getFlag(flags, names, fallback));
141
+ if (value === true || value === false) return value;
142
+ if (value === undefined || value === '') return fallback;
143
+ if (String(value) === 'true') return true;
144
+ if (String(value) === 'false') return false;
145
+ throw new Error(`--${names[0]} must be true or false.`);
146
+ }
147
+
103
148
  function requireFlag(flags, names, label = names[0]) {
104
149
  const value = getFlag(flags, names);
105
150
  if (value === undefined || value === true || value === '') throw new Error(`Missing required --${label}.`);
@@ -166,7 +211,7 @@ async function fileExists(path) {
166
211
  }
167
212
 
168
213
  function pathFlag(flags, names, fallback) {
169
- const value = getFlag(flags, names, fallback);
214
+ const value = lastFlagValue(getFlag(flags, names, fallback));
170
215
  if (value === true || value === '') throw new Error(`Missing required --${names[0]}.`);
171
216
  return String(value);
172
217
  }
@@ -210,6 +255,74 @@ async function quickstartMemoryTextFromFlags(flags) {
210
255
  return DEFAULT_QUICKSTART_MEMORY;
211
256
  }
212
257
 
258
+ function quickstartOutputs(bundleInput, outDirInput) {
259
+ const bundlePath = resolve(bundleInput);
260
+ const outDirPath = resolve(outDirInput);
261
+ const contextPackPath = resolve(outDirPath, QUICKSTART_ARTIFACT_NAMES.contextPack);
262
+ const exportPath = resolve(outDirPath, QUICKSTART_ARTIFACT_NAMES.export);
263
+ const verifyReportPath = resolve(outDirPath, QUICKSTART_ARTIFACT_NAMES.verifyReport);
264
+ return {
265
+ bundlePath,
266
+ outDirPath,
267
+ contextPackPath,
268
+ exportPath,
269
+ verifyReportPath,
270
+ contextPackDisplay: quickstartPathDisplay(outDirInput, QUICKSTART_ARTIFACT_NAMES.contextPack),
271
+ exportDisplay: quickstartPathDisplay(outDirInput, QUICKSTART_ARTIFACT_NAMES.export),
272
+ verifyReportDisplay: quickstartPathDisplay(outDirInput, QUICKSTART_ARTIFACT_NAMES.verifyReport),
273
+ outputs: [
274
+ { path: bundlePath, display: bundleInput },
275
+ { path: contextPackPath, display: quickstartPathDisplay(outDirInput, QUICKSTART_ARTIFACT_NAMES.contextPack) },
276
+ { path: exportPath, display: quickstartPathDisplay(outDirInput, QUICKSTART_ARTIFACT_NAMES.export) },
277
+ { path: verifyReportPath, display: quickstartPathDisplay(outDirInput, QUICKSTART_ARTIFACT_NAMES.verifyReport) },
278
+ ],
279
+ };
280
+ }
281
+
282
+ async function buildQuickstartArtifacts(flags, { bundleInput = DEFAULT_BUNDLE, outDirInput = dirname(bundleInput), overwrite = false, write = true } = {}) {
283
+ const paths = quickstartOutputs(bundleInput, outDirInput);
284
+ ensureDistinctOutputPaths(paths.outputs.map((output) => output.path));
285
+ await assertCanWriteQuickstartOutputs(paths.outputs, overwrite);
286
+
287
+ const vault = createVault({
288
+ subjectId: String(getFlag(flags, ['subject', 'subject-id'], 'local-user')),
289
+ displayName: String(getFlag(flags, ['display-name', 'name'], 'Local user')),
290
+ passphrase: String(getFlag(flags, ['passphrase'], 'local-development-passphrase')),
291
+ });
292
+ const passport = createPassport({
293
+ vault,
294
+ subjectId: vault.subject_id,
295
+ displayName: String(getFlag(flags, ['display-name', 'name'], 'Local user')),
296
+ });
297
+ remember({
298
+ vault,
299
+ passport,
300
+ text: await quickstartMemoryTextFromFlags(flags),
301
+ purpose: 'quickstart_local_proof',
302
+ purpose_tags: ['quickstart'],
303
+ metadata: { source: 'enigma quickstart' },
304
+ });
305
+ const contextPack = compileContextPack({
306
+ vault,
307
+ passport,
308
+ query: '',
309
+ purpose: 'quickstart_local_context',
310
+ limit: 8,
311
+ });
312
+ const exported = exportBundle({ vault, includePlaintext: false });
313
+ const bundle = exported.bundle ?? exported;
314
+ const verifyReport = verifyBundle(bundle);
315
+
316
+ if (write) {
317
+ await writeJson(paths.bundlePath, bundle);
318
+ await writeJson(paths.contextPackPath, contextPack);
319
+ await writeJson(paths.exportPath, bundle);
320
+ await writeJson(paths.verifyReportPath, verifyReport);
321
+ }
322
+
323
+ return { ...paths, vault, passport, contextPack, bundle, verifyReport };
324
+ }
325
+
213
326
  async function crossModelMemoryTextFromFlags(flags) {
214
327
  const textFile = getFlag(flags, ['memory-file', 'memoryFile', 'text-file', 'textFile']);
215
328
  if (textFile === undefined) return DEFAULT_CROSS_MODEL_MEMORY;
@@ -273,6 +386,157 @@ function publicContextPackSummary(pack) {
273
386
  };
274
387
  }
275
388
 
389
+ const SEARCH_RELEVANCE_STOPWORDS = new Set([
390
+ 'about',
391
+ 'after',
392
+ 'again',
393
+ 'against',
394
+ 'also',
395
+ 'and',
396
+ 'any',
397
+ 'are',
398
+ 'assistant',
399
+ 'because',
400
+ 'been',
401
+ 'before',
402
+ 'being',
403
+ 'between',
404
+ 'can',
405
+ 'could',
406
+ 'current',
407
+ 'does',
408
+ 'from',
409
+ 'has',
410
+ 'have',
411
+ 'how',
412
+ 'into',
413
+ 'its',
414
+ 'latest',
415
+ 'more',
416
+ 'most',
417
+ 'number',
418
+ 'own',
419
+ 'owns',
420
+ 'please',
421
+ 'should',
422
+ 'that',
423
+ 'the',
424
+ 'their',
425
+ 'then',
426
+ 'there',
427
+ 'these',
428
+ 'they',
429
+ 'this',
430
+ 'use',
431
+ 'using',
432
+ 'was',
433
+ 'what',
434
+ 'when',
435
+ 'where',
436
+ 'which',
437
+ 'who',
438
+ 'whose',
439
+ 'why',
440
+ 'with',
441
+ 'would',
442
+ ]);
443
+
444
+ function addSearchToken(tokens, token) {
445
+ if (token.length < 3) return;
446
+ if (!/[a-z]/u.test(token)) return;
447
+ if (SEARCH_RELEVANCE_STOPWORDS.has(token)) return;
448
+ tokens.add(token);
449
+ }
450
+
451
+ function searchTokensFrom(value) {
452
+ const tokens = new Set();
453
+ if (value === undefined || value === null) return tokens;
454
+ for (const match of String(value).toLowerCase().matchAll(/[a-z0-9]+(?:[-_][a-z0-9]+)*/gu)) {
455
+ const token = match[0];
456
+ addSearchToken(tokens, token);
457
+ if (token.includes('-') || token.includes('_')) {
458
+ for (const part of token.split(/[-_]+/u)) addSearchToken(tokens, part);
459
+ }
460
+ }
461
+ return tokens;
462
+ }
463
+
464
+ function addSearchTokensFromValue(tokens, value) {
465
+ for (const token of searchTokensFrom(value)) tokens.add(token);
466
+ }
467
+
468
+ function recordSearchTokens(record, content) {
469
+ const tokens = new Set();
470
+ addSearchTokensFromValue(tokens, content);
471
+ addSearchTokensFromValue(tokens, record?.kind);
472
+ for (const tag of record?.purpose_tags ?? []) addSearchTokensFromValue(tokens, tag);
473
+ return tokens;
474
+ }
475
+
476
+ function searchScore(queryTokens, memoryTokens) {
477
+ if (queryTokens.size === 0) return 0;
478
+ let overlap = 0;
479
+ for (const token of queryTokens) {
480
+ if (memoryTokens.has(token)) overlap += 1;
481
+ }
482
+ return Math.round((overlap / queryTokens.size) * 1_000_000) / 1_000_000;
483
+ }
484
+
485
+ function searchResultReceiptIds(vault, memoryAddr) {
486
+ return vault.receipts
487
+ .filter((receipt) => receipt?.memory_addr === memoryAddr || receipt?.source_addr === memoryAddr)
488
+ .map((receipt) => receipt.receipt_id)
489
+ .filter((receiptId) => typeof receiptId === 'string' && receiptId.length > 0);
490
+ }
491
+
492
+ function publicAccessReceiptRef(receipt) {
493
+ return {
494
+ access_receipt_ref: `enigma://memory-access/${receipt.receipt_id}`,
495
+ receipt_id: receipt.receipt_id,
496
+ operation: receipt.operation,
497
+ memory_addr: receipt.address,
498
+ plan_hash: receipt.plan_hash,
499
+ estimated_prompt_tokens: receipt.estimated_prompt_tokens,
500
+ access_boundary: receipt.access_boundary,
501
+ };
502
+ }
503
+
504
+ function searchCandidates(vault, queryTokens) {
505
+ const candidates = [];
506
+ const byAddress = new Map();
507
+ for (const memoryAddr of [...vault.activeAddresses].sort()) {
508
+ const record = vault.__getRecord(memoryAddr);
509
+ if (!record || record.state !== 'active') continue;
510
+ const content = vault.__getPlaintext(memoryAddr);
511
+ const score = searchScore(queryTokens, recordSearchTokens(record, content));
512
+ if (queryTokens.size > 0 && score === 0) continue;
513
+ const candidate = {
514
+ address: memoryAddr,
515
+ content,
516
+ importance: typeof record.importance === 'number' ? record.importance : typeof record.confidence === 'number' ? record.confidence : undefined,
517
+ last_accessed_at: record.updated_at ?? record.created_at,
518
+ metadata: {
519
+ kind: record.kind,
520
+ sensitivity: record.sensitivity,
521
+ purpose_tags: record.purpose_tags ?? [],
522
+ },
523
+ };
524
+ candidates.push(candidate);
525
+ byAddress.set(memoryAddr, { record, content, score });
526
+ }
527
+ return { candidates, byAddress };
528
+ }
529
+
530
+ function connectorReadinessSummary(bundlePath) {
531
+ return {
532
+ ready: true,
533
+ bundle: bundlePath,
534
+ bundle_env: 'ENIGMA_BUNDLE',
535
+ mcp_command: 'enigma-mcp',
536
+ supported_clients: supportedClients,
537
+ };
538
+ }
539
+
276
540
  function demoBundleRef(bundleWasSupplied) {
277
541
  return bundleWasSupplied ? 'supplied_bundle' : DEFAULT_CROSS_MODEL_DEMO_BUNDLE;
278
542
  }
@@ -493,85 +757,380 @@ async function initCommand(flags, io) {
493
757
  return 0;
494
758
  }
495
759
 
496
- export async function quickstartCommand(flags, io) {
760
+ function setupClientIds(flags) {
761
+ const raw = getFlag(flags, ['client']);
762
+ if (raw === undefined) return { mode: 'default', auto: false, clients: [...DEFAULT_SETUP_CLIENTS], explicit_clients: [] };
763
+ const values = Array.isArray(raw) ? raw : [raw];
764
+ const clients = [];
765
+ let auto = false;
766
+ for (const value of values) {
767
+ if (value === true || value === '') throw new Error('Missing required --client.');
768
+ for (const client of String(value).split(',').map((item) => item.trim()).filter(Boolean)) {
769
+ if (client === 'auto') {
770
+ auto = true;
771
+ continue;
772
+ }
773
+ getClientProfile(client);
774
+ if (!clients.includes(client)) clients.push(client);
775
+ }
776
+ }
777
+ if (auto) return { mode: 'auto', auto: true, clients, explicit_clients: clients };
778
+ return { mode: clients.length > 0 ? 'explicit' : 'default', auto: false, clients: clients.length > 0 ? clients : [...DEFAULT_SETUP_CLIENTS], explicit_clients: clients };
779
+ }
780
+
781
+ function setupDetectedClientReason(client) {
782
+ if (client.installed === true && client.recommended_action === 'already_configured') return 'already_configured';
783
+ if (client.installed === true) return 'installed_needs_repair';
784
+ return 'client_config_present';
785
+ }
786
+
787
+ function setupSkippedClientReason(client) {
788
+ if (client.parse_error === true) return 'config_json_invalid';
789
+ if (client.config_path_exists === false || client.exists === false) return 'client_config_missing';
790
+ if (client.ok === false) return 'config_unreadable';
791
+ return 'not_selected';
792
+ }
793
+
794
+ function publicSetupClientSelectionEntry(client, reason) {
795
+ return {
796
+ client_id: client.client_id,
797
+ display_name: client.display_name,
798
+ reason,
799
+ action: client.recommended_action ?? client.action ?? null,
800
+ installed: client.installed === true,
801
+ config_path_exists: client.config_path_exists === true || client.exists === true,
802
+ };
803
+ }
804
+
805
+ async function setupAutoClientSelection(flags, artifacts, fallbackClients, mode) {
806
+ const doctor = await doctorConnectors({
807
+ ...connectorOptions(flags),
808
+ bundlePath: artifacts.bundlePath,
809
+ redactPaths: true,
810
+ });
811
+ const detected = doctor.clients.filter((client) => (client.config_path_exists === true || client.exists === true) && client.parse_error !== true);
812
+ const fallbackUsed = detected.length === 0;
813
+ const selectedClients = fallbackUsed ? [...fallbackClients] : detected.map((client) => client.client_id);
814
+ const selectedSet = new Set(selectedClients);
815
+ const detectedSet = new Set(detected.map((client) => client.client_id));
816
+ const selected = fallbackUsed
817
+ ? selectedClients.map((clientId) => {
818
+ const client = doctor.clients.find((entry) => entry.client_id === clientId) ?? { client_id: clientId, display_name: getClientProfile(clientId).display_name };
819
+ return publicSetupClientSelectionEntry(client, 'default_fallback_no_client_configs_detected');
820
+ })
821
+ : detected.map((client) => publicSetupClientSelectionEntry(client, setupDetectedClientReason(client)));
822
+ const skipped = doctor.clients
823
+ .filter((client) => !selectedSet.has(client.client_id))
824
+ .map((client) => publicSetupClientSelectionEntry(client, fallbackUsed && !detectedSet.has(client.client_id) ? 'not_in_default_fallback' : setupSkippedClientReason(client)));
825
+ const connectableClientIds = new Set(detected.map((client) => client.client_id));
826
+ return {
827
+ mode,
828
+ auto: true,
829
+ fallback_used: fallbackUsed,
830
+ clients: selectedClients,
831
+ selected,
832
+ skipped,
833
+ connectable_client_ids: connectableClientIds,
834
+ detection: doctor,
835
+ };
836
+ }
837
+
838
+ function setupMemorySource(flags) {
839
+ if (getFlag(flags, ['memory-file', 'memoryFile', 'text-file', 'textFile']) !== undefined) return 'memory_file';
840
+ if (getFlag(flags, ['memory-text', 'memoryText']) !== undefined) return 'demo_text';
841
+ return 'default_demo';
842
+ }
843
+
844
+ function commandPath(path) {
845
+ return `"${String(path).replace(/"/g, '\\"')}"`;
846
+ }
847
+
848
+ function publicPathDisplay(path, label) {
849
+ const value = String(path);
850
+ if (/^[A-Za-z]:[\\/]/.test(value) || value.startsWith('/') || value.startsWith('\\\\')) return `<${label}>`;
851
+ return value;
852
+ }
853
+
854
+ function setupPublicDisplays(bundleInput, outDirInput) {
855
+ const outDir = publicPathDisplay(outDirInput, 'out-dir');
856
+ return {
857
+ bundle: publicPathDisplay(bundleInput, 'bundle-path'),
858
+ context_pack: quickstartPathDisplay(outDir, QUICKSTART_ARTIFACT_NAMES.contextPack),
859
+ export: quickstartPathDisplay(outDir, QUICKSTART_ARTIFACT_NAMES.export),
860
+ verify_report: quickstartPathDisplay(outDir, QUICKSTART_ARTIFACT_NAMES.verifyReport),
861
+ };
862
+ }
863
+
864
+ function setupRawDisplays(bundleInput, outDirInput) {
865
+ const plan = quickstartOutputs(bundleInput, outDirInput);
866
+ return {
867
+ bundle: plan.outputs[0].display,
868
+ context_pack: plan.contextPackDisplay,
869
+ export: plan.exportDisplay,
870
+ verify_report: plan.verifyReportDisplay,
871
+ };
872
+ }
873
+
874
+ function publicSetupError(error, rawDisplays, publicDisplays) {
875
+ let message = error.message;
876
+ for (const [raw, safe] of Object.entries({
877
+ [rawDisplays.bundle]: publicDisplays.bundle,
878
+ [rawDisplays.context_pack]: publicDisplays.context_pack,
879
+ [rawDisplays.export]: publicDisplays.export,
880
+ [rawDisplays.verify_report]: publicDisplays.verify_report,
881
+ })) {
882
+ message = message.split(raw).join(safe);
883
+ }
884
+ return new Error(message);
885
+ }
886
+
887
+ function setupNextCommands(bundleInput, exportDisplay, clients, writeConnectors) {
888
+ const primaryClient = clients[0] ?? DEFAULT_SETUP_CLIENTS[0];
889
+ const commands = [
890
+ `enigma remember --bundle ${commandPath(bundleInput)} --text-file ./memory.txt`,
891
+ `enigma search --bundle ${commandPath(bundleInput)} --query "project context"`,
892
+ `enigma context --bundle ${commandPath(bundleInput)} --query "project context"`,
893
+ `enigma verify --export ${commandPath(exportDisplay)}`,
894
+ ];
895
+ if (!writeConnectors) commands.push(`enigma connect ${primaryClient} --bundle ${commandPath(bundleInput)}`);
896
+ return commands;
897
+ }
898
+
899
+ async function setupDoctorChecks(flags, artifacts, clients, displays) {
900
+ const packageJson = await readPackageJson();
901
+ const requiredNodeMajor = minimumNodeMajor(packageJson.engines?.node);
902
+ const currentNodeMajor = nodeMajor(process.versions.node);
903
+ const binMap = packageJson.bin && typeof packageJson.bin === 'object' && !Array.isArray(packageJson.bin) ? packageJson.bin : {};
904
+ const binEntries = await Promise.all(REQUIRED_PACKAGE_BINS.map(async (name) => {
905
+ const target = binMap[name];
906
+ const declared = typeof target === 'string' && target.length > 0;
907
+ return {
908
+ name,
909
+ target: declared ? target : null,
910
+ declared,
911
+ exists: declared ? await fileExists(packageFileUrl(target)) : false,
912
+ };
913
+ }));
914
+ const schemas = await schemaFiles();
915
+ const connectorBaseOptions = {
916
+ ...connectorOptions(flags),
917
+ bundlePath: artifacts.bundlePath,
918
+ redactPaths: true,
919
+ };
920
+ const connectorClients = [];
921
+ for (const client of clients) {
922
+ const doctor = await doctorConnectors({ ...connectorBaseOptions, clientId: client });
923
+ connectorClients.push(...doctor.clients);
924
+ }
925
+ const checks = {
926
+ node: {
927
+ ok: requiredNodeMajor === 0 || currentNodeMajor >= requiredNodeMajor,
928
+ current: process.versions.node,
929
+ required: packageJson.engines?.node ?? null,
930
+ },
931
+ package_bins: {
932
+ ok: binEntries.every((entry) => entry.declared && entry.exists),
933
+ required: REQUIRED_PACKAGE_BINS,
934
+ entries: binEntries,
935
+ missing: binEntries.filter((entry) => !entry.declared).map((entry) => entry.name),
936
+ missing_targets: binEntries.filter((entry) => entry.declared && !entry.exists).map((entry) => entry.name),
937
+ },
938
+ artifacts: {
939
+ ok: artifacts.verifyReport.ok === true,
940
+ bundle: displays.bundle,
941
+ context_pack: displays.context_pack,
942
+ export: displays.export,
943
+ verify_report: displays.verify_report,
944
+ },
945
+ schemas: {
946
+ ok: schemas.length > 0,
947
+ count: schemas.length,
948
+ files: schemas,
949
+ },
950
+ connectors: {
951
+ ok: connectorClients.every((client) => client.ok !== false),
952
+ clients: connectorClients,
953
+ },
954
+ };
955
+ return { ok: Object.values(checks).every((check) => check.ok !== false), checks };
956
+ }
957
+
958
+ function publicConnectPlan(plan, wizard, profile, snippet) {
959
+ const changed = plan?.changed !== false;
960
+ const dryRun = plan?.dryRun === true || plan?.dry_run === true;
961
+ const plannedWrites = changed ? [{ type: 'write', path: wizard.default_config_path }] : [];
962
+ return {
963
+ ok: plan?.ok !== false,
964
+ action: 'connect',
965
+ client_id: profile.client_id,
966
+ configPath: wizard.default_config_path,
967
+ config_path: wizard.default_config_path,
968
+ serverName: profile.server_name,
969
+ server_name: profile.server_name,
970
+ changed,
971
+ dryRun,
972
+ dry_run: dryRun,
973
+ writes_performed: changed && !dryRun,
974
+ backup_planned: Boolean(plan?.backupPath),
975
+ plannedWrites,
976
+ planned_writes: plannedWrites,
977
+ config: snippet,
978
+ };
979
+ }
980
+
981
+ async function setupConnectorPlans(flags, artifacts, clients, writeConnectors, displays, writeClientIds = null) {
982
+ const publicOptions = {
983
+ ...connectorOptions(flags),
984
+ bundlePath: displays.bundle,
985
+ };
986
+ const writeOptions = {
987
+ ...connectorOptions(flags),
988
+ bundlePath: artifacts.bundlePath,
989
+ };
990
+ const connectors = [];
991
+ for (const client of clients) {
992
+ const profile = getClientProfile(client, publicOptions);
993
+ const snippet = renderMcpConfig(client, publicOptions);
994
+ const wizard = planConnectWizard(client, { platform: profile.platform }).clients[0];
995
+ const writeAllowed = writeClientIds === null || writeClientIds.has(client);
996
+ const rawPlan = writeConnectors && writeAllowed
997
+ ? await connectClient(client, { ...writeOptions, dryRun: false })
998
+ : { ok: true, changed: !(writeConnectors && !writeAllowed), dryRun: true };
999
+ const plan = publicConnectPlan(rawPlan, wizard, profile, snippet);
1000
+ connectors.push({
1001
+ client_id: client,
1002
+ display_name: profile.display_name,
1003
+ default_config_path: wizard.default_config_path,
1004
+ mcp_config_snippet: snippet,
1005
+ connect_command: `enigma connect ${client} --bundle ${commandPath(displays.bundle)}`,
1006
+ connect_plan: plan,
1007
+ write_selected: writeAllowed,
1008
+ write_skipped_reason: writeConnectors && !writeAllowed ? 'client_config_missing' : null,
1009
+ wizard,
1010
+ });
1011
+ }
1012
+ return connectors;
1013
+ }
1014
+
1015
+ function publicSetupClientSelection(selection) {
1016
+ return {
1017
+ mode: selection.mode,
1018
+ auto: selection.auto === true,
1019
+ fallback_used: selection.fallback_used === true,
1020
+ selected: selection.selected,
1021
+ skipped: selection.skipped,
1022
+ };
1023
+ }
1024
+
1025
+ export async function setupCommand(flags, io) {
497
1026
  const bundleInput = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
498
1027
  const outDirInput = pathFlag(flags, ['out-dir', 'outDir'], dirname(bundleInput));
499
- const bundlePath = resolve(bundleInput);
500
- const outDirPath = resolve(outDirInput);
501
- const contextPackPath = resolve(outDirPath, 'context-pack.json');
502
- const exportPath = resolve(outDirPath, 'export.json');
503
- const verifyReportPath = resolve(outDirPath, 'verify-report.json');
504
- const contextPackDisplay = quickstartPathDisplay(outDirInput, 'context-pack.json');
505
- const exportDisplay = quickstartPathDisplay(outDirInput, 'export.json');
506
- const verifyReportDisplay = quickstartPathDisplay(outDirInput, 'verify-report.json');
507
- const outputs = [
508
- { path: bundlePath, display: bundleInput },
509
- { path: contextPackPath, display: contextPackDisplay },
510
- { path: exportPath, display: exportDisplay },
511
- { path: verifyReportPath, display: verifyReportDisplay },
512
- ];
513
- ensureDistinctOutputPaths(outputs.map((output) => output.path));
514
- const overwrite = getFlag(flags, ['overwrite'], false) === true || getFlag(flags, ['overwrite'], false) === 'true';
515
- await assertCanWriteQuickstartOutputs(outputs, overwrite);
1028
+ const requestedSelection = setupClientIds(flags);
1029
+ const overwrite = booleanFlag(flags, ['overwrite'], false);
1030
+ const dryRun = booleanFlag(flags, ['dry-run', 'dryRun'], false);
1031
+ const writeConnectorsFlag = booleanFlag(flags, ['write-connectors', 'writeConnectors'], false);
1032
+ const connectInstalled = booleanFlag(flags, ['connect-installed', 'connectInstalled'], false);
1033
+ const connectorWritesRequested = (writeConnectorsFlag || connectInstalled) && !dryRun;
1034
+ const displays = setupPublicDisplays(bundleInput, outDirInput);
1035
+ const rawDisplays = setupRawDisplays(bundleInput, outDirInput);
1036
+ let artifacts;
1037
+ try {
1038
+ artifacts = await buildQuickstartArtifacts(flags, { bundleInput, outDirInput, overwrite, write: !dryRun });
1039
+ } catch (error) {
1040
+ throw publicSetupError(error, rawDisplays, displays);
1041
+ }
1042
+ const selection = connectInstalled || requestedSelection.auto
1043
+ ? await setupAutoClientSelection(flags, artifacts, DEFAULT_SETUP_CLIENTS, connectInstalled ? 'connect_installed' : 'auto')
1044
+ : {
1045
+ ...requestedSelection,
1046
+ fallback_used: false,
1047
+ selected: requestedSelection.clients.map((clientId) => {
1048
+ const profile = getClientProfile(clientId);
1049
+ return publicSetupClientSelectionEntry({ client_id: clientId, display_name: profile.display_name }, requestedSelection.mode === 'default' ? 'default_setup_client' : 'explicit_client');
1050
+ }),
1051
+ skipped: [],
1052
+ connectable_client_ids: null,
1053
+ };
1054
+ const clients = selection.clients;
1055
+ const writeClientIds = connectInstalled ? selection.connectable_client_ids : null;
1056
+ const connectors = await setupConnectorPlans(flags, artifacts, clients, connectorWritesRequested, displays, writeClientIds);
1057
+ const doctor = await setupDoctorChecks(flags, artifacts, clients, displays);
1058
+ const ok = artifacts.verifyReport.ok === true;
1059
+ const anyConnectorWritePerformed = connectors.some((connector) => connector.connect_plan.writes_performed === true);
516
1060
 
517
- const vault = createVault({
518
- subjectId: String(getFlag(flags, ['subject', 'subject-id'], 'local-user')),
519
- displayName: String(getFlag(flags, ['display-name', 'name'], 'Local user')),
520
- passphrase: String(getFlag(flags, ['passphrase'], 'local-development-passphrase')),
521
- });
522
- const passport = createPassport({
523
- vault,
524
- subjectId: vault.subject_id,
525
- displayName: String(getFlag(flags, ['display-name', 'name'], 'Local user')),
526
- });
527
- remember({
528
- vault,
529
- passport,
530
- text: await quickstartMemoryTextFromFlags(flags),
531
- purpose: 'quickstart_local_proof',
532
- purpose_tags: ['quickstart'],
533
- metadata: { source: 'enigma quickstart' },
534
- });
535
- const contextPack = compileContextPack({
536
- vault,
537
- passport,
538
- query: '',
539
- purpose: 'quickstart_local_context',
540
- limit: 8,
541
- });
542
- const exported = exportBundle({ vault, includePlaintext: false });
543
- const bundle = exported.bundle ?? exported;
544
- const verifyReport = verifyBundle(bundle);
1061
+ print({
1062
+ ok,
1063
+ schema: 'enigma.setup.v1',
1064
+ command: 'enigma setup',
1065
+ dry_run: dryRun,
1066
+ artifacts_written: !dryRun,
1067
+ client_configs_written: writeConnectorsFlag && !dryRun ? true : anyConnectorWritePerformed,
1068
+ client_config_write_requested: connectorWritesRequested,
1069
+ connector_write_mode: connectInstalled ? 'installed_only' : (writeConnectorsFlag ? 'selected_clients' : 'plan_only'),
1070
+ connect_installed: connectInstalled,
1071
+ bundle: displays.bundle,
1072
+ context_pack: displays.context_pack,
1073
+ export: displays.export,
1074
+ verify_report: displays.verify_report,
1075
+ memory_source: setupMemorySource(flags),
1076
+ memory_plaintext_echoed: false,
1077
+ memory_count: Array.isArray(artifacts.bundle.memory_objects) ? artifacts.bundle.memory_objects.length : 0,
1078
+ receipt_count: Array.isArray(artifacts.bundle.receipts) ? artifacts.bundle.receipts.length : 0,
1079
+ context_item_count: Array.isArray(artifacts.contextPack.memories) ? artifacts.contextPack.memories.length : 0,
1080
+ verify_ok: artifacts.verifyReport.ok === true,
1081
+ provider_credentials_required: false,
1082
+ provider_native_memory_canonical: false,
1083
+ selected_clients: clients,
1084
+ skipped_clients: selection.skipped,
1085
+ client_selection: publicSetupClientSelection(selection),
1086
+ connector_write_skips: connectors
1087
+ .filter((connector) => connector.write_skipped_reason)
1088
+ .map((connector) => ({
1089
+ client_id: connector.client_id,
1090
+ display_name: connector.display_name,
1091
+ reason: connector.write_skipped_reason,
1092
+ })),
1093
+ connectors,
1094
+ mcp_config_snippets: Object.fromEntries(connectors.map((connector) => [connector.client_id, connector.mcp_config_snippet])),
1095
+ connect_plans: Object.fromEntries(connectors.map((connector) => [connector.client_id, connector.connect_plan])),
1096
+ next_commands: setupNextCommands(displays.bundle, displays.export, clients, connectorWritesRequested && (!connectInstalled || anyConnectorWritePerformed)),
1097
+ checks: doctor.checks,
1098
+ claim_boundaries: { ...SETUP_CLAIM_BOUNDARIES },
1099
+ }, io);
1100
+ return ok ? 0 : 1;
1101
+ }
545
1102
 
546
- await writeJson(bundlePath, bundle);
547
- await writeJson(contextPackPath, contextPack);
548
- await writeJson(exportPath, bundle);
549
- await writeJson(verifyReportPath, verifyReport);
1103
+ export async function quickstartCommand(flags, io) {
1104
+ const bundleInput = pathFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE);
1105
+ const outDirInput = pathFlag(flags, ['out-dir', 'outDir'], dirname(bundleInput));
1106
+ const overwrite = booleanFlag(flags, ['overwrite'], false);
1107
+ const artifacts = await buildQuickstartArtifacts(flags, { bundleInput, outDirInput, overwrite, write: true });
550
1108
 
551
1109
  print({
552
- ok: verifyReport.ok === true,
1110
+ ok: artifacts.verifyReport.ok === true,
553
1111
  bundle: bundleInput,
554
- context_pack: contextPackDisplay,
555
- export: exportDisplay,
556
- verify_report: verifyReportDisplay,
557
- memory_count: Array.isArray(bundle.memory_objects) ? bundle.memory_objects.length : 0,
558
- receipt_count: Array.isArray(bundle.receipts) ? bundle.receipts.length : 0,
559
- context_item_count: Array.isArray(contextPack.memories) ? contextPack.memories.length : 0,
560
- verify_ok: verifyReport.ok === true,
1112
+ context_pack: artifacts.contextPackDisplay,
1113
+ export: artifacts.exportDisplay,
1114
+ verify_report: artifacts.verifyReportDisplay,
1115
+ memory_count: Array.isArray(artifacts.bundle.memory_objects) ? artifacts.bundle.memory_objects.length : 0,
1116
+ receipt_count: Array.isArray(artifacts.bundle.receipts) ? artifacts.bundle.receipts.length : 0,
1117
+ context_item_count: Array.isArray(artifacts.contextPack.memories) ? artifacts.contextPack.memories.length : 0,
1118
+ verify_ok: artifacts.verifyReport.ok === true,
561
1119
  next_commands: [
562
- `enigma verify --export ${exportDisplay}`,
1120
+ `enigma verify --export ${artifacts.exportDisplay}`,
563
1121
  `enigma connect generic-mcp --bundle ${bundleInput} --dry-run`,
564
1122
  ],
565
1123
  claim_boundaries: {
566
1124
  local_only: true,
567
1125
  provider_credentials_required: false,
1126
+ provider_native_memory_canonical: false,
568
1127
  provider_deletion_proof: false,
569
1128
  model_forgetting_proof: false,
570
1129
  roi_or_savings_guarantee: false,
571
1130
  compliance_certification: false,
572
1131
  },
573
1132
  }, io);
574
- return verifyReport.ok === true ? 0 : 1;
1133
+ return artifacts.verifyReport.ok === true ? 0 : 1;
575
1134
  }
576
1135
 
577
1136
  export async function crossModelDemoCommand(flags, io) {
@@ -767,6 +1326,118 @@ async function contextCommand(flags, io) {
767
1326
  return 0;
768
1327
  }
769
1328
 
1329
+
1330
+ async function searchCommand(flags, io) {
1331
+ const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1332
+ const query = String(requireFlag(flags, ['query', 'q'], 'query'));
1333
+ const limit = integerFlag(flags, ['limit'], 'limit', 8);
1334
+ if (limit < 0) throw new Error('--limit must be non-negative.');
1335
+ const includeContent = getFlag(flags, ['include-content', 'includeContent']) === true || getFlag(flags, ['include-content', 'includeContent']) === 'true';
1336
+ const { vault } = await loadState(bundlePath);
1337
+ const roots = vault.__computeRoots();
1338
+ const queryTokens = searchTokensFrom(query);
1339
+ const { candidates, byAddress } = searchCandidates(vault, queryTokens);
1340
+ const plan = createMemoryOptimizationPlan({
1341
+ candidates,
1342
+ prompt: query,
1343
+ now: getFlag(flags, ['now'], '2026-01-01T00:00:00.000Z'),
1344
+ });
1345
+ const planIndex = new Map(plan.items.map((item, index) => [item.address, index]));
1346
+ const selectedItems = plan.items
1347
+ .filter((item) => byAddress.has(item.address))
1348
+ .sort((left, right) => {
1349
+ const scoreDiff = byAddress.get(right.address).score - byAddress.get(left.address).score;
1350
+ if (scoreDiff !== 0) return scoreDiff;
1351
+ return planIndex.get(left.address) - planIndex.get(right.address);
1352
+ })
1353
+ .slice(0, limit);
1354
+ const accessReceipts = selectedItems.map((item, index) => createMemoryAccessReceipt({
1355
+ item,
1356
+ plan,
1357
+ sequence: index,
1358
+ timestamp: null,
1359
+ pricing: plan.pricing,
1360
+ }));
1361
+ const accessReceiptByAddress = new Map(accessReceipts.map((receipt) => [receipt.address, publicAccessReceiptRef(receipt)]));
1362
+ const results = selectedItems.map((item) => {
1363
+ const hit = byAddress.get(item.address);
1364
+ const record = hit.record;
1365
+ const accessReceipt = accessReceiptByAddress.get(item.address);
1366
+ return {
1367
+ memory_ref: `enigma://memory/${item.address}`,
1368
+ memory_addr: item.address,
1369
+ address: item.address,
1370
+ kind: record.kind,
1371
+ sensitivity: record.sensitivity,
1372
+ tags: Array.isArray(record.purpose_tags) ? [...record.purpose_tags] : [],
1373
+ purpose_tags: Array.isArray(record.purpose_tags) ? [...record.purpose_tags] : [],
1374
+ score: hit.score,
1375
+ tier: item.tier,
1376
+ receipt_ids: searchResultReceiptIds(vault, item.address),
1377
+ access_receipt_ref: accessReceipt?.access_receipt_ref,
1378
+ access_receipt_id: accessReceipt?.receipt_id,
1379
+ access_receipt_refs: accessReceipt?.access_receipt_ref ? [accessReceipt.access_receipt_ref] : [],
1380
+ content_redacted: !includeContent,
1381
+ ...(includeContent ? { content: hit.content } : {}),
1382
+ };
1383
+ });
1384
+ print({
1385
+ ok: true,
1386
+ schema: 'enigma.memory_search.v1',
1387
+ bundle: bundlePath,
1388
+ query_redacted: true,
1389
+ limit,
1390
+ result_count: results.length,
1391
+ results,
1392
+ access_receipts: accessReceipts.map(publicAccessReceiptRef),
1393
+ active_set_root: roots.active_set_root,
1394
+ receipt_log_root: roots.receipt_log_root,
1395
+ claim_boundary: includeContent
1396
+ ? 'Search ran against the selected local bundle and includes plaintext only because --include-content was explicit; this does not prove provider deletion, provider-native memory state, or model forgetting.'
1397
+ : 'Search ran against the selected local bundle and redacts plaintext by default; refs, scores, tags, roots, and receipt refs are not provider deletion proof or model forgetting proof.',
1398
+ }, io);
1399
+ return 0;
1400
+ }
1401
+
1402
+ async function statusCommand(flags, io) {
1403
+ const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
1404
+ const { stored, vault, passport } = await loadState(bundlePath);
1405
+ const roots = vault.__computeRoots();
1406
+ const activeCount = activeMemoryCount(vault);
1407
+ const tombstoneCount = vault.tombstones instanceof Map ? vault.tombstones.size : 0;
1408
+ const receiptCount = Array.isArray(vault.receipts) ? vault.receipts.length : 0;
1409
+ print({
1410
+ ok: true,
1411
+ schema: 'enigma.passport_status.v1',
1412
+ bundle: bundlePath,
1413
+ passport_ref: `enigma://passport/${passport.passport_id}`,
1414
+ owner: {
1415
+ subject_id: stored.owner?.subject_id ?? stored.passport?.owner?.subject_id ?? stored.vault?.subject_id ?? passport.owner?.subject_id ?? vault.subject_id,
1416
+ display_name: stored.owner?.display_name ?? stored.passport?.owner?.display_name ?? stored.vault?.display_name ?? passport.owner?.display_name ?? 'Local user',
1417
+ },
1418
+ counts: {
1419
+ active_memories: activeCount,
1420
+ tombstoned_memories: tombstoneCount,
1421
+ receipts: receiptCount,
1422
+ },
1423
+ active_memory_count: activeCount,
1424
+ tombstoned_memory_count: tombstoneCount,
1425
+ receipt_count: receiptCount,
1426
+ active_set_root: roots.active_set_root,
1427
+ receipt_log_root: roots.receipt_log_root,
1428
+ connector_readiness: connectorReadinessSummary(bundlePath),
1429
+ next_recommended_commands: [
1430
+ `enigma remember --bundle "${bundlePath}" --text-file <path>`,
1431
+ `enigma search --bundle "${bundlePath}" --query <text>`,
1432
+ `enigma context --bundle "${bundlePath}" --query <text>`,
1433
+ `enigma verify --bundle "${bundlePath}"`,
1434
+ `enigma connect <client> --bundle "${bundlePath}"`,
1435
+ ],
1436
+ claim_boundary: 'Status reports local bundle counters, owner display fields, connector readiness hints, and commitment roots only; it does not expose raw memory, certify compliance, prove provider deletion, or prove model forgetting.',
1437
+ }, io);
1438
+ return 0;
1439
+ }
1440
+
770
1441
  async function exportCommand(flags, io) {
771
1442
  const bundlePath = resolve(String(getFlag(flags, ['bundle', 'file'], DEFAULT_BUNDLE)));
772
1443
  const { vault } = await loadState(bundlePath);
@@ -1272,6 +1943,7 @@ function usage() {
1272
1943
  usage: 'enigma <command> [options]',
1273
1944
  commands: [
1274
1945
  'init',
1946
+ 'setup',
1275
1947
  'quickstart',
1276
1948
  'demo cross-model',
1277
1949
  'doctor',
@@ -1283,6 +1955,9 @@ function usage() {
1283
1955
  'update',
1284
1956
  'delete',
1285
1957
  'context',
1958
+ 'search',
1959
+ 'status',
1960
+ 'passport status',
1286
1961
  'export',
1287
1962
  'import <source>',
1288
1963
  'capsule export',
@@ -1318,6 +1993,28 @@ function usage() {
1318
1993
  '--text <text>': 'Inline local memory text. Avoid for private content because argv can be logged by process tooling.',
1319
1994
  '--text-file <path>': 'Read local memory text from a file so private smoke input is not exposed in shell argv. Aliases: --memory-file, --textFile, --memoryFile.',
1320
1995
  },
1996
+ search_options: {
1997
+ '--query <text>': 'Required local query. Output redacts the query and memory plaintext by default. Alias: --q.',
1998
+ '--bundle <path>': 'Bundle JSON to search. Defaults to .enigma/bundle.json.',
1999
+ '--limit <n>': 'Maximum ranked active memories to return. Defaults to 8.',
2000
+ '--json': 'Reserved for explicit JSON output; CLI output is JSON by default.',
2001
+ '--include-content': 'Opt in to returning plaintext local memory content in the JSON result.',
2002
+ },
2003
+ status_options: {
2004
+ 'enigma status --bundle <path>': 'Show local Memory Passport counts, roots, owner display fields, connector readiness, and next commands.',
2005
+ 'enigma passport status --bundle <path>': 'Alias for enigma status.',
2006
+ },
2007
+ setup_options: {
2008
+ '--bundle <path>': 'Bundle JSON to create. Defaults to .enigma/bundle.json.',
2009
+ '--out-dir <path>': 'Directory for context-pack.json, export.json, and verify-report.json. Defaults to the bundle directory.',
2010
+ '--client <id|auto>': `Client to plan; repeat or comma-separate. Use auto to plan installed/config-present clients, falling back to ${DEFAULT_SETUP_CLIENTS.join(', ')}.`,
2011
+ '--connect-installed': 'Auto-select installed/config-present clients and write only those existing client configs; missing client configs are reported and skipped.',
2012
+ '--memory-file <path>': 'Read local memory text from a file without echoing plaintext. Alias: --text-file.',
2013
+ '--memory-text <text>': 'Inline demo-only memory text. Avoid for private content because argv can be logged.',
2014
+ '--overwrite': 'Replace existing local setup artifacts.',
2015
+ '--dry-run': 'Plan setup without writing local artifacts or client configs.',
2016
+ '--write-connectors': 'Also write selected client MCP config files. Defaults to false.',
2017
+ },
1321
2018
  quickstart_options: {
1322
2019
  '--bundle <path>': 'Bundle JSON to create. Defaults to .enigma/bundle.json.',
1323
2020
  '--out-dir <path>': 'Directory for context-pack.json, export.json, and verify-report.json. Defaults to the bundle directory.',
@@ -1398,15 +2095,16 @@ export async function main(argv = process.argv.slice(2), io = { stdout: process.
1398
2095
  print(usage(), io);
1399
2096
  return 0;
1400
2097
  }
1401
- const twoPartCommands = ['boundary', 'mcp', 'mesh', 'enterprise', 'capsule', 'relay', 'gateway', 'connect', 'disconnect', 'import', 'native-host', 'meter', 'settlement', 'demo'];
2098
+ const twoPartCommands = ['boundary', 'mcp', 'mesh', 'enterprise', 'capsule', 'relay', 'gateway', 'connect', 'disconnect', 'import', 'native-host', 'meter', 'settlement', 'demo', 'passport'];
1402
2099
  const flags = parseArgs(twoPartCommands.includes(command) ? argv.slice(2) : argv.slice(1));
1403
2100
  const positionalFile = optionalPositional(argv[2]);
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'))) {
2101
+ if ((flags.has('help') || argv.includes('-h')) && (command === 'setup' || command === 'search' || command === 'status' || (command === 'passport' && subcommand === 'status') || ((command === 'relay' || command === 'gateway') && (subcommand === 'serve' || subcommand === 'demo')) || (command === 'native-host' && (subcommand === 'manifest' || subcommand === 'install-plan')) || (command === 'demo' && subcommand === 'cross-model'))) {
1405
2102
  print(usage(), io);
1406
2103
  return 0;
1407
2104
  }
1408
2105
  try {
1409
2106
  if (command === 'init') return await initCommand(flags, io);
2107
+ if (command === 'setup') return await setupCommand(flags, io);
1410
2108
  if (command === 'quickstart') return await quickstartCommand(flags, io);
1411
2109
  if (command === 'demo' && subcommand === 'cross-model') return await crossModelDemoCommand(flags, io);
1412
2110
  if (command === 'doctor') return await doctorCommand(flags, io);
@@ -1418,6 +2116,9 @@ export async function main(argv = process.argv.slice(2), io = { stdout: process.
1418
2116
  if (command === 'update') return await updateCommand(flags, io);
1419
2117
  if (command === 'delete') return await deleteCommand(flags, io);
1420
2118
  if (command === 'context') return await contextCommand(flags, io);
2119
+ if (command === 'search') return await searchCommand(flags, io);
2120
+ if (command === 'status') return await statusCommand(flags, io);
2121
+ if (command === 'passport' && subcommand === 'status') return await statusCommand(flags, io);
1421
2122
  if (command === 'export') return await exportCommand(flags, io);
1422
2123
  if (command === 'import') return await importCommand(subcommand, flags, io, positionalFile);
1423
2124
  if (command === 'capsule' && subcommand === 'export') return await capsuleExportCommand(flags, io, positionalFile);
@@ -2,6 +2,41 @@
2
2
 
3
3
  Enigma Memory is a local-first SDK, CLI, MCP server, and service-contract package. The developer surfaces are designed to be copied without secrets, cloud credentials, hidden local paths, or account identifiers.
4
4
 
5
+ ## CLI memory passport loop
6
+
7
+ For most developers, start with the installed CLI before reading the SDK internals or service contracts:
8
+
9
+ ```sh
10
+ npm install -g enigma-memory
11
+ enigma setup --overwrite
12
+ ```
13
+
14
+ `enigma setup --overwrite` is the safe default. It writes local Enigma artifacts under the workspace `.enigma` path and emits deterministic, public-safe JSON without printing raw memory plaintext. It does not write third-party app configs.
15
+
16
+ To let setup auto-detect installed or already-configured clients and report the connector plan without mutating client configs:
17
+
18
+ ```sh
19
+ enigma setup --client auto --overwrite
20
+ ```
21
+
22
+ To explicitly write connector entries for installed/config-present clients only:
23
+
24
+ ```sh
25
+ enigma setup --connect-installed --overwrite
26
+ ```
27
+
28
+ `--client auto` selects clients found by connector detection and falls back to the default setup client list when none are present. The setup output lists selected clients, skipped clients, and skip reasons. `--connect-installed` implies auto selection, writes only for installed/config-present clients, and skips missing configs instead of creating every default client config. Only explicit write flags mutate client configs; `enigma connect <client>` without `--dry-run` remains the single-client write path, and existing `enigma setup --write-connectors` behavior for explicit/default clients is unchanged. Treat provider-native memory as non-canonical cache only; the local Enigma vault is canonical.
29
+
30
+ After setup, use the same local vault from the CLI or connected clients:
31
+
32
+ ```sh
33
+ enigma remember --text-file ./memory.txt
34
+ enigma search --query "..."
35
+ enigma context --query "..." --optimize
36
+ enigma verify --export ./.enigma/export.json
37
+ enigma connect claude-desktop --dry-run
38
+ ```
39
+
5
40
  ## Copyable starting points
6
41
 
7
42
  - SDK/API guide: [`docs/sdk-api.md`](./sdk-api.md)
@@ -25,11 +60,11 @@ The example app prints ids, counts, roots, and verification status only. It does
25
60
 
26
61
  ## CLI and CI loop
27
62
 
28
- The CI example installs Node 24, installs the published `enigma-memory@0.1.4` package, runs:
63
+ The CI example installs Node 24, installs the published `enigma-memory@0.1.7` package, runs:
29
64
 
30
65
  ```sh
31
- npx enigma quickstart --overwrite
32
- npx enigma doctor
66
+ npx --yes --package enigma-memory enigma setup --overwrite
67
+ npx --yes --package enigma-memory enigma doctor
33
68
  npm run benchmark:memory-suite -- --out benchmark-report.json
34
69
  ```
35
70
 
@@ -49,22 +84,73 @@ Use the workflow as a template in a consumer repository. It is intentionally lim
49
84
 
50
85
  ## MCP client loop
51
86
 
52
- The generic MCP template uses the installed `enigma-mcp` command and exactly one environment placeholder:
87
+ The same installed package can be used by Claude Desktop, Cursor, Kimi Code, or any generic MCP client. The smooth setup path is `enigma setup --client auto --overwrite` to plan detected clients, then `enigma setup --connect-installed --overwrite` only when you explicitly want setup to write installed/config-present client configs. Manual snippets remain useful when a client needs a copied entry; replace the bundle path with the local path from your setup output, and restart the client.
88
+
89
+ Claude Desktop:
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "enigma": {
95
+ "command": "enigma-mcp",
96
+ "args": [],
97
+ "env": {
98
+ "ENIGMA_BUNDLE": "/absolute/path/to/.enigma/bundle.json"
99
+ }
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ Cursor:
106
+
107
+ ```json
108
+ {
109
+ "mcpServers": {
110
+ "enigma": {
111
+ "command": "enigma-mcp",
112
+ "args": [],
113
+ "env": {
114
+ "ENIGMA_BUNDLE": "/absolute/path/to/.enigma/bundle.json"
115
+ }
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ Kimi Code:
122
+
123
+ ```json
124
+ {
125
+ "mcpServers": {
126
+ "enigma": {
127
+ "command": "enigma-mcp",
128
+ "args": [],
129
+ "env": {
130
+ "ENIGMA_BUNDLE": "/absolute/path/to/.enigma/bundle.json"
131
+ }
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ Generic MCP:
53
138
 
54
139
  ```json
55
140
  {
56
141
  "mcpServers": {
57
142
  "enigma": {
58
143
  "command": "enigma-mcp",
144
+ "args": [],
59
145
  "env": {
60
- "ENIGMA_BUNDLE": "<ENIGMA_BUNDLE>"
146
+ "ENIGMA_BUNDLE": "/absolute/path/to/.enigma/bundle.json"
61
147
  }
62
148
  }
63
149
  }
64
150
  }
65
151
  ```
66
152
 
67
- Replace `<ENIGMA_BUNDLE>` with the bundle file you control, or with the client-specific environment expansion syntax if your MCP client supports it. Do not commit private bundle paths if they reveal local usernames, workspace names, account ids, or other personal details.
153
+ Do not commit private bundle paths if they reveal local usernames, workspace names, account ids, or other personal details.
68
154
 
69
155
  ## Claim boundaries for developers
70
156
 
package/docs/sdk-api.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SDK and API guide
2
2
 
3
- This guide covers the public package imports for `enigma-memory@0.1.5`. The SDK runs locally by default: vaults, passports, context packs, receipts, relay/gateway demo state, storage contracts, metering artifacts, and settlement artifacts are package-level developer surfaces. They are not evidence of hosted Enigma cloud, provider-side deletion, provider model forgetting, token ROI, invoice savings, compliance certification, or benchmark leadership.
3
+ This guide covers the public package imports for `enigma-memory@0.1.7`. The SDK runs locally by default: vaults, passports, context packs, receipts, relay/gateway demo state, storage contracts, metering artifacts, and settlement artifacts are package-level developer surfaces. They are not evidence of hosted Enigma cloud, provider-side deletion, provider model forgetting, token ROI, invoice savings, compliance certification, or benchmark leadership.
4
4
 
5
5
  ## Install and import style
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enigma-memory",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "Provider-agnostic AI memory passport and offline-verifiable proof layer.",
6
6
  "license": "Apache-2.0",
@@ -17,7 +17,7 @@ import {
17
17
  const DEFAULT_BUNDLE = '.enigma/bundle.json';
18
18
  const JSONRPC_VERSION = '2.0';
19
19
  const MCP_PROTOCOL_VERSION = '2024-11-05';
20
- const SERVER_INFO = Object.freeze({ name: 'enigma-mcp-server', version: '0.1.5' });
20
+ const SERVER_INFO = Object.freeze({ name: 'enigma-mcp-server', version: '0.1.7' });
21
21
  const JSON_RPC_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
22
22
  const JSON_RPC_ERROR = Object.freeze({
23
23
  INVALID_REQUEST: -32600,
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
6
6
 
7
7
  export const INSTALLER_ASSET_SCHEMA = 'enigma.installer_assets.v1';
8
8
  export const INSTALLER_ASSET_PACKAGE = 'enigma-memory';
9
- export const INSTALLER_ASSET_VERSION = '0.1.5';
9
+ export const INSTALLER_ASSET_VERSION = '0.1.7';
10
10
  export const INSTALLER_ASSET_GENERATED_AT = '1970-01-01T00:00:00.000Z';
11
11
 
12
12
  const SCRIPT_PATH = fileURLToPath(import.meta.url);
@@ -995,7 +995,7 @@ function buildSuiteReport(datasetRows, topK, options) {
995
995
  generated_at: options.generated_at ?? new Date().toISOString(),
996
996
  package: {
997
997
  name: 'enigma-memory',
998
- version: '0.1.5',
998
+ version: '0.1.7',
999
999
  },
1000
1000
  public_safe: true,
1001
1001
  top_k: topK,