@hraness/message-like-me 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/LICENSE +21 -0
  3. package/README.md +698 -0
  4. package/SECURITY.md +318 -0
  5. package/dist/agentic-messaging-v1.d.ts +179 -0
  6. package/dist/agentic-messaging-v1.js +52 -0
  7. package/dist/canonical-json.d.ts +3 -0
  8. package/dist/cli-bs3db5jr.js +643 -0
  9. package/dist/cli-d7qv38ab.js +485 -0
  10. package/dist/cli-kw20gkk3.js +5 -0
  11. package/dist/cli-qqafdvz9.js +5 -0
  12. package/dist/cli-ry4128kz.js +584 -0
  13. package/dist/cli-ththzwja.js +20 -0
  14. package/dist/cli-x1qncxm7.js +1078 -0
  15. package/dist/cli.js +9436 -0
  16. package/dist/ensoul-source-v1.d.ts +121 -0
  17. package/dist/ensoul-source-v1.js +24 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.js +47 -0
  20. package/dist/message-bundle-v1-identity.d.ts +2 -0
  21. package/dist/message-bundle-v1.d.ts +205 -0
  22. package/dist/message-bundle-v1.js +37 -0
  23. package/dist/message-bundle-v2-identity.d.ts +2 -0
  24. package/dist/message-bundle-v2.d.ts +215 -0
  25. package/dist/message-bundle-v2.js +43 -0
  26. package/dist/metrics.d.ts +41 -0
  27. package/dist/types.d.ts +568 -0
  28. package/docs/local-message-bundle-v1.md +245 -0
  29. package/docs/local-message-bundle-v2.md +167 -0
  30. package/docs/methodology.md +322 -0
  31. package/docs/research.md +164 -0
  32. package/package.json +82 -0
  33. package/schema/ensoul-messages-source-v1.schema.json +248 -0
  34. package/schema/local-message-bundle-v1.schema.json +449 -0
  35. package/schema/local-message-bundle-v2.schema.json +462 -0
  36. package/schema/style-profile-v1.schema.json +223 -0
  37. package/schema/style-profile-v2.schema.json +202 -0
  38. package/skills/ensoul/LICENSE +23 -0
  39. package/skills/ensoul/NOTICE.md +7 -0
  40. package/skills/ensoul/SKILL.md +226 -0
  41. package/skills/ensoul/VENDORED_FROM.md +7 -0
  42. package/skills/ensoul/agents/openai.yaml +4 -0
  43. package/skills/ensoul/references/ensoul-source-packet-v1.schema.json +187 -0
  44. package/skills/ensoul/references/evidence-method.md +148 -0
  45. package/skills/ensoul/references/output-blueprint.md +143 -0
  46. package/skills/ensoul/references/source-packets.md +139 -0
  47. package/skills/ensoul/scripts/prepare_x_archive.py +467 -0
  48. package/skills/ensoul/scripts/validate_source_packet.py +477 -0
  49. package/skills/message-like-me/SKILL.md +229 -0
  50. package/skills/message-like-me/agents/openai.yaml +4 -0
  51. package/skills/message-like-me/references/analysis.md +159 -0
  52. package/skills/message-like-me/references/drafting.md +86 -0
  53. package/skills/message-like-me/references/ensoul.md +94 -0
  54. package/skills/message-like-me/references/evaluation.md +81 -0
  55. package/skills/message-like-me/references/privacy.md +106 -0
  56. package/skills/message-like-me/references/profile-schema.md +148 -0
package/README.md ADDED
@@ -0,0 +1,698 @@
1
+ # Message Like Me
2
+
3
+ **A local-first CLI and Agent Skill for studying private messaging history and
4
+ drafting messages that sound like you.**
5
+
6
+ Message Like Me turns private local messaging history into deterministic
7
+ conversation metrics, bounded study packets, reusable style profiles, and
8
+ privacy-bounded evidence packets for the Ensoul person-model workflow. It
9
+ reads native iMessage history, caller-owned X data archives, and strict local
10
+ source bundles, including multi-account Beeper and native WhatsApp exports
11
+ produced through Wrench. Its copied Message Like Me and Ensoul Agent Skills
12
+ teach Codex, Claude, and other coding agents how to interpret those local
13
+ artifacts, build revisable evidence-led person models, and draft unsent replies
14
+ in your voice.
15
+
16
+ The CLI has no model integration. It does not authenticate with a product
17
+ account, invoke Wrench, Beeper, Wacli, or WhatsApp, send messages, or operate
18
+ Messages. An agent you already run may use the installed skill for semantic
19
+ analysis and drafting; opening a study or Ensoul packet exposes its bounded
20
+ excerpts to that agent environment.
21
+
22
+ This is an evidence layer for relationship-aware drafting. It does not train a
23
+ model, represent your identity, infer your beliefs, or claim that a draft is
24
+ what you would have written. Your current meaning, facts, and intent outrank
25
+ historical style.
26
+
27
+ ## Supported sources
28
+
29
+ | Source | What Message Like Me reads | Boundary |
30
+ | --- | --- | --- |
31
+ | Apple Messages | The current macOS user's native `chat.db` history | Read-only ingestion from an ownership-checked stable local copy; Messages is never operated or changed. |
32
+ | X data archive | Direct-message history in a caller-owned archive ZIP | X Chat is not included; the importer does not contact X, extract the archive, or download media. |
33
+ | Beeper via Wrench | A bounded local bundle produced by verified Wrench v0.16.1 with Beeper CLI 0.6.2 | Message Like Me reads the finished bundle; it receives no Beeper credential, invokes no Wrench or Beeper operation, and sends nothing. |
34
+ | WhatsApp via Wrench | A one-account native bundle produced by Wrench v0.16.3 with official Wacli 0.15.0 | Message Like Me verifies the finished bundle; Wrench alone owns Wacli, linked-device authentication, synchronization, and provider operations. |
35
+ | macOS Contacts | Optional names and exact email or phone handles from AddressBook | Label enrichment only; Contacts is not a messaging-history source and is never changed. |
36
+
37
+ ## Install
38
+
39
+ Message Like Me requires Bun 1.3.14 or newer. Install the immutable public
40
+ release from GitHub, then install both bundled Agent Skills:
41
+
42
+ ```sh
43
+ bun add --global github:hraness/message-like-me#v0.8.0
44
+ messagelikeme skill install
45
+ ```
46
+
47
+ Start a new agent session after installing the `message-like-me` and `ensoul`
48
+ skills. The command preflights and stages both copied directories before it
49
+ publishes either one; `--force` replaces both as one installation. The default
50
+ target is Codex at user scope. Other supported targets and project-local
51
+ installation are available explicitly:
52
+
53
+ ```sh
54
+ messagelikeme skill install --target claude
55
+ messagelikeme skill install --target agents --scope project
56
+ messagelikeme skill path
57
+ ```
58
+
59
+ Message Like Me is distributed directly through GitHub and is not published to
60
+ npm.
61
+
62
+ ## Start with private local history
63
+
64
+ Initialize the private data store and inspect its location:
65
+
66
+ ```sh
67
+ messagelikeme init
68
+ messagelikeme doctor --json
69
+ ```
70
+
71
+ On macOS, the default store is:
72
+
73
+ ```text
74
+ ~/Library/Application Support/Message Like Me/
75
+ ```
76
+
77
+ The directory is private to the current user. It contains a local SQLite
78
+ database, stored profiles, and a private installation key used to derive
79
+ stable pseudonymous IDs. Study packets are written only to the explicit path
80
+ you choose. You can put the store elsewhere by placing
81
+ `--data-dir /absolute/private/path` before the command.
82
+
83
+ Import the current user's iMessage database:
84
+
85
+ ```sh
86
+ messagelikeme ingest imessage --json
87
+ ```
88
+
89
+ The default source is the current user's Messages `chat.db`. Use `--database`
90
+ only to name another caller-owned physical database:
91
+
92
+ ```sh
93
+ messagelikeme ingest imessage --database /absolute/path/to/chat.db --json
94
+ ```
95
+
96
+ Ingestion validates the source schema and ownership, makes a stable private
97
+ copy of the database and its transactional sidecars, and opens only that copy
98
+ with SQLite. It does not change Messages, `chat.db`, or its sidecars. macOS may
99
+ require permission for the terminal or agent host to read Messages data.
100
+
101
+ Import direct messages from a caller-owned X data archive ZIP:
102
+
103
+ ```sh
104
+ messagelikeme ingest x-archive \
105
+ --input /absolute/private/path/x-data-archive.zip \
106
+ --json
107
+ ```
108
+
109
+ The importer requires a normalized absolute path to an owner-only physical ZIP
110
+ and reads only the supported archive entries. It does not extract the archive,
111
+ evaluate its JavaScript wrappers, access X or another network, or download
112
+ linked media. It stores exact archive and account provenance with the normalized
113
+ source so a later audit can identify which local export supplied the evidence.
114
+ The supported source is the archive's direct-message history. X Chat history is
115
+ not included. Bounded reply and mention identity metadata from selected tweet
116
+ entries may help associate provider user IDs with X handles or display names;
117
+ tweet bodies never become messaging-style evidence.
118
+
119
+ X data archives do not expose whether a direct message used an explicit reply
120
+ link. Those messages still contribute body, direction, ordering, tempo, and
121
+ response-shape evidence, but reply observability is marked unavailable. They do
122
+ not enter the explicit-reply ratio and are not treated as observed non-replies.
123
+
124
+ When the same X account is already present through a Beeper bundle, first find
125
+ that source in the redacted inventory, then name it explicitly:
126
+
127
+ ```sh
128
+ messagelikeme sources list --json
129
+ messagelikeme ingest x-archive \
130
+ --input /absolute/private/path/x-data-archive.zip \
131
+ --overlap-source <beeper-x-source-id> \
132
+ --json
133
+ ```
134
+
135
+ `--overlap-source` is not a fuzzy merge switch. Reconciliation proceeds only
136
+ for one-to-one direct conversations whose account identity, peer handle, and
137
+ overlapping message evidence match exactly. Both source provenances remain
138
+ inspectable, ambiguity or contradiction fails closed, and exact messages appear
139
+ once in analysis. Group DMs remain separate because the legacy archive does not
140
+ supply enough cross-provider sender proof for exact equivalence. Reimporting the
141
+ same or a later archive preserves proven deduplication; archive absence does not
142
+ delete retained history.
143
+
144
+ To study accounts connected through Beeper, install the currently verified
145
+ [`@hraness/wrench@0.16.1`](https://www.npmjs.com/package/@hraness/wrench/v/0.16.1)
146
+ package from npm, then use Wrench to create a new private Message Like Me
147
+ bundle:
148
+
149
+ ```sh
150
+ bun add --global @hraness/wrench@0.16.1
151
+ wrench beeper export-message-like-me \
152
+ --auth <beeper-auth-id> \
153
+ --output /absolute/private/path/beeper-bundle \
154
+ --json
155
+ ```
156
+
157
+ The optional `--limit-chats`, `--limit-messages`, and `--max-participants`
158
+ flags lower the export bounds. The output path must be a normalized absolute
159
+ path to a directory that does not already exist. Wrench v0.16.1 calls the
160
+ pinned [official Beeper CLI 0.6.2 release](https://github.com/beeper/cli/releases/tag/v0%2E6%2E2)
161
+ directly. It enumerates
162
+ the connected account realm, invokes `export --no-attachments` once per
163
+ account in deterministic order, and reports the account ordinal, elapsed-time
164
+ heartbeats, and cumulative validated chat and message counts on stderr. It
165
+ retains each private raw shard until it can atomically publish the complete
166
+ mode-`0700` seven-file bundle with mode-`0600` files.
167
+
168
+ The export does not use the separate
169
+ [Beeper Desktop API MCP project](https://github.com/beeper/desktop-api-mcp).
170
+ The CLI path supplies the bounded account snapshots and local files needed for
171
+ hash validation, deterministic conversion, crash recovery, and atomic
172
+ publication. Provider URLs and credentials are excluded. Message Like Me does
173
+ not receive the Beeper credential, start Wrench, invoke a Beeper operation, or
174
+ send a message.
175
+
176
+ Ingest the finished directory, then inspect its redacted source health:
177
+
178
+ ```sh
179
+ messagelikeme ingest bundle --input /absolute/private/path/beeper-bundle --json
180
+ messagelikeme sources list --json
181
+ messagelikeme sources show <source-id> --json
182
+ ```
183
+
184
+ The importer verifies the fixed version-one inventory, canonical UTF-8 NDJSON,
185
+ record and byte bounds, owner-only permissions, artifact digests, and manifest
186
+ digest before changing the store. One bundle may contain several connected
187
+ accounts and networks; each becomes a separate source namespace. Native
188
+ iMessage and prior bundle sources remain alongside it.
189
+
190
+ The interchange, integrity, identity, and reimport laws are in the
191
+ [version-one local message bundle contract](docs/local-message-bundle-v1.md).
192
+ Message Like Me accepts bundle schema `1` with source ID `beeper-local` and
193
+ source-transform version `1.1.0`. Wrench v0.16.1 is the currently verified
194
+ producer. Compatibility is determined by those exact manifest coordinates,
195
+ not by an open-ended Wrench package range.
196
+
197
+ Beeper exports describe bounded local observations. A later bounded export
198
+ that omits an older record does not delete retained history. Explicit deletion,
199
+ removal, replacement, and tombstone records suppress their target, and a later
200
+ reappearance restores it. Older snapshots cannot overwrite newer state. Use
201
+ `sources show <source-id> --private --json` only when you deliberately need the
202
+ private provider account and source metadata.
203
+
204
+ For native WhatsApp evidence, install Wrench v0.16.3 and let its official
205
+ Wacli 0.15.0 adapter create the one-account v2 bundle:
206
+
207
+ ```sh
208
+ bun add --global @hraness/wrench@0.16.3
209
+ wrench whatsapp export-message-like-me \
210
+ --auth <whatsapp-auth-id> \
211
+ --output /absolute/private/path/whatsapp-bundle \
212
+ --json
213
+
214
+ messagelikeme ingest bundle \
215
+ --input /absolute/private/path/whatsapp-bundle \
216
+ --json
217
+ ```
218
+
219
+ Message Like Me accepts exactly bundle schema `2`, source
220
+ `wacli-local@1.0.0`, provider `whatsapp@0.15.0`, and network `whatsapp`. The
221
+ bundle admits canonical WhatsApp user, LID, and group JIDs; only an exact
222
+ E.164-backed user JID supplies a Contacts-match phone handle. Status,
223
+ broadcast, newsletter, credential, session-database, provider-URL, and media-byte
224
+ surfaces are excluded. The complete contract is in
225
+ [local message bundle v2](docs/local-message-bundle-v2.md).
226
+
227
+ If a Beeper WhatsApp source already represents the same exact account, inspect
228
+ the redacted source inventory and name it explicitly:
229
+
230
+ ```sh
231
+ messagelikeme ingest bundle \
232
+ --input /absolute/private/path/whatsapp-bundle \
233
+ --overlap-source <beeper-whatsapp-source-id> \
234
+ --json
235
+ ```
236
+
237
+ Reconciliation requires exact self and direct-peer E.164 identity plus an
238
+ unambiguous shared text-message fingerprint. Groups, bodyless messages, names,
239
+ phone suffixes, and approximate timestamps cannot prove equivalence. Both
240
+ provenances and source-unique history remain. Native Wacli evidence becomes the
241
+ preferred `whatsappJid` route; its proven Beeper duplicate remains
242
+ `evidence-only` with reason `superseded-route`.
243
+
244
+ Optionally enrich and join direct conversations with private identities from
245
+ macOS Contacts:
246
+
247
+ ```sh
248
+ messagelikeme ingest contacts --json
249
+ ```
250
+
251
+ The default source is the current user's AddressBook directory. An explicit
252
+ absolute AddressBook root, `Sources` directory, store directory, or
253
+ `AddressBook-vN.abcddb` file can be selected with `--addressbook`:
254
+
255
+ ```sh
256
+ messagelikeme ingest contacts \
257
+ --addressbook /absolute/path/to/AddressBook \
258
+ --json
259
+ ```
260
+
261
+ Contacts ingest may run before or after any message source. It reads only
262
+ bounded name, email, and phone fields from a stable private copy. Exact
263
+ normalized email or E.164 phone handles can join several one-to-one threads
264
+ for the same AddressBook person into one analysis scope. An imported
265
+ conversation is eligible only when its source positively establishes a
266
+ complete direct participant roster. Existing conversation IDs remain aliases
267
+ for that person scope. Shared handles remain ambiguous, local phone numbers
268
+ never gain a guessed country code, unmatched threads stay separate, and groups
269
+ are never collapsed to one person. Contact labels have their own revision, so
270
+ a rename does not stale a messaging-style profile. `messagelikeme doctor`
271
+ reports local aggregate state without asking for an account or credential.
272
+
273
+ ## Inspect behavior without exposing prose
274
+
275
+ Contact listings and aggregate views omit private labels, handles, and message
276
+ bodies by default:
277
+
278
+ ```sh
279
+ messagelikeme contacts list --min-outgoing 20 --json
280
+ messagelikeme contacts show <contact-id> --json
281
+ messagelikeme inspect tempo <contact-id> --session-gap 28800 --burst-gap 300 --json
282
+ messagelikeme inspect sessions <contact-id> --limit 20 --json
283
+ ```
284
+
285
+ The metrics cover conversation start and end, message counts, incoming and
286
+ outgoing turns, within-session response latency, single-message versus
287
+ multi-message replies, surface prose features, multi-point response contexts,
288
+ reactions, and explicit reply use. Incoming messages establish what you were
289
+ responding to; they are never counted as examples of your writing style.
290
+ Sessions, bursts, and response episodes never cross a source conversation
291
+ boundary. Person scopes spanning several apps expose a sorted `services`
292
+ breakdown instead of hiding the mixed-channel evidence behind a null service.
293
+ Reactions with no provider timestamp still contribute to reaction counts and
294
+ direction, but never to temporal metrics. Raw provider reaction values remain
295
+ private; ordinary metrics and drafting context expose only fixed-size counts,
296
+ direction, datedness, and the outgoing reaction ratio. Session and burst gaps
297
+ are configurable seconds and are recorded with each result. They are
298
+ segmentation choices, not universal facts about conversation.
299
+
300
+ Explicit-reply metrics also report how many outgoing text messages were
301
+ eligible for reply measurement and how many came from a source where reply
302
+ links were unavailable. The ratio uses only eligible messages. Do not compare
303
+ an X archive's unavailable reply metadata with an observed no-reply decision
304
+ from iMessage or a compatible bundle.
305
+
306
+ Pass `--private` to `contacts list` or `contacts show` only when you need to
307
+ resolve a pseudonymous contact to its local private label or participants.
308
+
309
+ When you already know the complete Contacts label, resolve only that exact
310
+ private name instead of listing every label:
311
+
312
+ ```sh
313
+ messagelikeme contacts resolve "Exact Contact Name" --private --json
314
+ ```
315
+
316
+ Resolution is normalized for case and Unicode representation, but it does not
317
+ perform prefix, substring, phonetic, or fuzzy matching. It returns only direct
318
+ person scopes and labels, never handles or message bodies.
319
+
320
+ ## Prepare an exact private agent handoff
321
+
322
+ Message Like Me can bind an ordered unsent draft to one exact local
323
+ source-conversation candidate and one current opaque Wrench context. It still
324
+ does not invoke Wrench, authenticate, launch a provider command, access a
325
+ network, or send a message.
326
+
327
+ Start with the redacted candidate inventory:
328
+
329
+ ```sh
330
+ messagelikeme routes list <contact-id> \
331
+ --output /absolute/private/routes.json --json
332
+ ```
333
+
334
+ A contact or person scope is never itself a destination. Each candidate names
335
+ one pseudonymous source and conversation inside that mode-`0600` output.
336
+ Ordinary stdout reports only its digest, counts, and selection state.
337
+ `--private` additionally reveals only
338
+ the exact account, source, and tagged conversation coordinate already observed
339
+ in that imported source: `beeperConversation` for a Beeper bundle,
340
+ `whatsappJid` for a native Wacli bundle, or `imessageChat` for Messages. It never
341
+ emits names, handles, participants, or a locator derived from them. Wrench
342
+ rejects a coordinate whose tag does not match the selected provider adapter.
343
+
344
+ An X archive candidate is always `evidence-only` with reason
345
+ `archive-source`. Handoff v1 also keeps group candidates evidence-only as an
346
+ explicit direct-conversation product limit. This does not claim that Wrench or
347
+ a provider cannot address an exact group. It prevents Message Like Me from
348
+ authorizing one through this first handoff contract. Several eligible direct
349
+ candidates produce an `ambiguous` selection state; no route is chosen from a
350
+ name, title, participant list, or merged person scope.
351
+
352
+ After Wrench has written its exact current context and the agent has written an
353
+ ordered one-to-eight-bubble draft, prepare one private handoff:
354
+
355
+ ```sh
356
+ messagelikeme handoff prepare <contact-id> \
357
+ --request /absolute/private/handoff-request.json \
358
+ --wrench-context /absolute/private/wrench-context.json \
359
+ --draft /absolute/private/draft.json \
360
+ --output /absolute/private/handoff.json \
361
+ --json
362
+ ```
363
+
364
+ The request contains the exact selected source-conversation candidate from the
365
+ private route inventory. The request, context, and draft must be owner-only,
366
+ singly linked physical files. The context must carry the pinned
367
+ `wrench.messaging-context-binding.v1` contract identity,
368
+ an unexpired opaque route and context reference, and exact SHA-256 data and
369
+ latest-message revisions. The output is a mode-`0600` file whose canonical
370
+ digest binds those values, the selected source revision, corpus and profile
371
+ evidence, bubble order, text, and optional reply references. Raw opaque Wrench
372
+ references and draft bodies appear only in the explicit private inputs and
373
+ handoff file.
374
+
375
+ Verification and audit commands emit hashes, counts, timestamps, and
376
+ pseudonymous IDs without bodies or raw Wrench references:
377
+
378
+ ```sh
379
+ messagelikeme handoff verify /absolute/private/handoff.json --json
380
+ messagelikeme handoff record <handoff-id> \
381
+ --wrench-receipt /absolute/private/wrench-receipt.json --json
382
+ messagelikeme handoffs show <handoff-id> --json
383
+ ```
384
+
385
+ Recording requires Wrench's pinned body-free receipt binding. It carries the
386
+ provider-neutral client-intent digest, set to this exact Message Like Me
387
+ handoff digest, along with route-reference, context-reference, exact-turn, and
388
+ private-preview digests. It also carries the proven prefix and a canonical
389
+ receipt digest. It contains no raw route or context reference. Message Like Me
390
+ stores only those hashes, counts, timestamps, states, and pseudonymous run and
391
+ handoff IDs. It never
392
+ inserts a sent message into the corpus. A later independent source ingestion
393
+ must observe that message before it can affect
394
+ style, tempo, reply, or interaction evidence. The pure contract is exported as
395
+ `@hraness/message-like-me/agentic-messaging-v1` for checked local consumers.
396
+
397
+ ## Prepare bounded evidence for Ensoul
398
+
399
+ Message Like Me can prepare one private messages-source packet for the local
400
+ owner or for one exact direct person resolved through Contacts:
401
+
402
+ ```sh
403
+ messagelikeme ensoul prepare <contact-id> \
404
+ --subject owner \
405
+ --output /absolute/private/path/owner-messages.json \
406
+ --after 2026-01-01T00:00:00.000Z \
407
+ --before 2026-08-01T00:00:00.000Z \
408
+ --limit 24 \
409
+ --json
410
+
411
+ messagelikeme ensoul prepare <person-id> \
412
+ --subject contact \
413
+ --output /absolute/private/path/contact-messages.json \
414
+ --limit 24 \
415
+ --json
416
+ ```
417
+
418
+ For `--subject contact`, `<person-id>` must be the exact `person_…` result of
419
+ `contacts resolve`; a conversation alias, unmatched direct thread, or group is
420
+ rejected. Both subject modes reject groups and multi-participant scopes. The
421
+ packet carries only redacted scope shape, conversation count, and sorted service
422
+ labels; private names, participants, and provider coordinates remain excluded.
423
+ The adapter reverses owner-relative direction before selection, so a
424
+ contact's attributable incoming prose becomes `authorRole: subject` and the
425
+ owner's prose becomes `counterpart`. For `--subject owner`, outgoing prose is
426
+ the subject's and incoming prose is counterpart context. A consumer must never
427
+ learn counterpart text as the subject's voice.
428
+
429
+ The output is a strict `ensoul.source-packet.v1` artifact with payload schema
430
+ `ensoul.messages-source.v1`. It reuses the deterministic diverse response
431
+ selector and the same default limits as a study packet: 24 response examples,
432
+ 4 KiB per text body, 12 messages per direction per example, and 256 KiB total.
433
+ It excludes system events, retractions, reactions, attachments, names, handles,
434
+ and raw provider coordinates. Scope revisions, inclusive `--after`, exclusive
435
+ `--before`, selection omissions, byte truncation, transport status, strong
436
+ source authorship, content and record digests, and the RFC 8785 canonical packet
437
+ digest remain explicit. The scope and body-free receipt also preserve the exact
438
+ session and burst gaps used for selection, including defaults. Selected prose
439
+ records use `contentRole: original`. Records sharing one pseudonymous
440
+ `provenance.runId` belong to the same selected
441
+ response context. Consumers must not join records from different context IDs.
442
+ The normalized sources cannot detect pasted quotations, forwarding, or AI
443
+ assistance; the packet states that limitation, and visible quoted material must
444
+ remain contextual. No claims are derived in the adapter.
445
+
446
+ The packet is written once to an explicit owner-controlled mode-`0600` physical
447
+ path; it never appears on stdout, and an existing file or symlink is not
448
+ overwritten. The JSON receipt contains only pseudonymous IDs, revisions,
449
+ digests, counts, bounds, and the chosen path. Run `$ensoul` in an agent
450
+ environment you authorize, open only that packet, and preserve its limitations.
451
+ Private messages are situated evidence, not a transcript, identity proof,
452
+ consent record, relationship label, diagnosis, stable personality, or authority
453
+ to impersonate or act for anyone.
454
+
455
+ ## Build a style profile
456
+
457
+ Aggregate metrics cannot explain why a short burst works in one context or why
458
+ a longer single message appears in another. For that semantic work, prepare a
459
+ small, diverse study packet at an explicit private path:
460
+
461
+ ```sh
462
+ messagelikeme study prepare <contact-id> \
463
+ --output /absolute/private/path/study.json \
464
+ --before 2026-08-01T00:00:00.000Z \
465
+ --limit 24 \
466
+ --json
467
+ ```
468
+
469
+ `study prepare`, `ensoul prepare`, `evaluate prepare`, and `handoff prepare` are
470
+ the only commands that write bounded message bodies outside the private
471
+ database. Their outputs are mode `0600`.
472
+ A study packet contains incoming context and outgoing responses selected across
473
+ different response shapes; it is not a full transcript export. By default,
474
+ each body is capped at 4 KiB, each example keeps at most 12 text messages per
475
+ direction, and the entire packet keeps at most 256 KiB of body text. Packet
476
+ coverage fields report every truncation or omission explicitly.
477
+
478
+ Keep the JSON receipt with the analysis. Its `packetSha256` binds the finished
479
+ profile to these exact packet bytes; the packet does not contain its own digest.
480
+
481
+ `--after` is inclusive and `--before` is exclusive. Temporal bounds let you
482
+ reserve later conversations for evaluation. Invoke `$message-like-me` in your
483
+ agent and ask it to analyze that contact. The skill separates measured facts
484
+ from inferred patterns, covers prose and tempo, studies how several inbound
485
+ points are handled, and treats reply links and tapbacks separately from written
486
+ text.
487
+
488
+ The agent writes a schema-version-two profile and asks the CLI to validate and
489
+ store it:
490
+
491
+ ```sh
492
+ messagelikeme profile apply /absolute/private/path/profile.json --json
493
+ messagelikeme profile show <contact-id> --json
494
+ ```
495
+
496
+ A version-two profile records the global corpus revision for provenance, a
497
+ person-and-window-specific evidence revision for validity, the exact
498
+ study-packet SHA-256, and the packet's non-body evidence manifest. Measured and
499
+ inferred claims cite valid packet example IDs and record counterexamples,
500
+ support counts, confidence, and drafting consequences. Messages for someone
501
+ else or outside the studied time window do not stale it; changes inside its
502
+ actual evidence do.
503
+
504
+ Export a profile only when you need an explicit private copy:
505
+
506
+ ```sh
507
+ messagelikeme profile export <contact-id> --output /absolute/private/path/profile.json
508
+ ```
509
+
510
+ Version-one profiles remain readable for migration, but new analyses should use
511
+ [`schema/style-profile-v2.schema.json`](schema/style-profile-v2.schema.json).
512
+
513
+ ## Audit against later conversations
514
+
515
+ Prepare a separate prompt and reference set from conversations after the study
516
+ cutoff:
517
+
518
+ ```sh
519
+ messagelikeme evaluate prepare <contact-id> \
520
+ --after 2026-08-01T00:00:00.000Z \
521
+ --prompt-output /absolute/private/path/evaluation-prompts.json \
522
+ --reference-output /absolute/private/path/evaluation-references.json \
523
+ --json
524
+ ```
525
+
526
+ Give the agent only the prompt file and fix one candidate bubble sequence per
527
+ case before opening the reference file. Then compare intent coverage, factual
528
+ meaning, prose, bubble shape, explicit replies, privacy leakage, and
529
+ calibration. The files support a blind workflow but do not enforce one, and the
530
+ historical response is one observation rather than a unique correct answer.
531
+ The CLI deliberately does not collapse these dimensions into a universal
532
+ fidelity score. See [the methodology](docs/methodology.md).
533
+
534
+ ## Draft an unsent reply
535
+
536
+ Ask an agent with the installed `$message-like-me` skill to draft for a
537
+ pseudonymous contact. The compact deterministic context is available through:
538
+
539
+ ```sh
540
+ messagelikeme context <contact-id> --json
541
+ ```
542
+
543
+ The skill preserves your intended meaning, selects the applicable profile,
544
+ and can express the result as one message or a realistic sequence of separate
545
+ bubbles. It uses explicit replies only when your evidence and the current
546
+ context support them.
547
+
548
+ Drafting ends with text in the agent task. Message Like Me has no send, react,
549
+ schedule, or messaging-application command.
550
+
551
+ ## Command reference
552
+
553
+ Run `messagelikeme --help` for the checked grammar. The public surfaces are:
554
+
555
+ ```text
556
+ messagelikeme init [--json]
557
+ messagelikeme ingest imessage [--database PATH] [--json]
558
+ messagelikeme ingest x-archive --input ABS_PATH
559
+ [--overlap-source SOURCE_ID] [--json]
560
+ messagelikeme ingest contacts [--addressbook PATH] [--json]
561
+ messagelikeme ingest bundle --input ABS_PATH
562
+ [--overlap-source SOURCE_ID] [--json]
563
+ messagelikeme sources list [--private] [--json]
564
+ messagelikeme sources show SOURCE_ID [--private] [--json]
565
+ messagelikeme contacts list [--min-outgoing N] [--limit N] [--private] [--json]
566
+ messagelikeme contacts show CONTACT_ID [--private] [--json]
567
+ messagelikeme contacts resolve QUERY --private [--limit N] [--json]
568
+ messagelikeme routes list CONTACT_ID --output FILE [--private] [--json]
569
+ messagelikeme inspect tempo CONTACT_ID [--session-gap N] [--burst-gap N] [--json]
570
+ messagelikeme inspect sessions CONTACT_ID [--limit N] [--session-gap N] [--burst-gap N] [--json]
571
+ messagelikeme study prepare CONTACT_ID --output FILE [--limit N]
572
+ [--after ISO_TIMESTAMP] [--before ISO_TIMESTAMP]
573
+ [--session-gap N] [--burst-gap N] [--json]
574
+ messagelikeme ensoul prepare CONTACT_ID --subject owner|contact --output FILE
575
+ [--limit N] [--after ISO_TIMESTAMP] [--before ISO_TIMESTAMP]
576
+ [--session-gap N] [--burst-gap N] [--json]
577
+ messagelikeme evaluate prepare CONTACT_ID --after ISO_TIMESTAMP
578
+ --prompt-output FILE --reference-output FILE [--before ISO_TIMESTAMP]
579
+ [--limit N] [--session-gap N] [--burst-gap N] [--json]
580
+ messagelikeme profile apply FILE [--json]
581
+ messagelikeme profile show CONTACT_ID [--json]
582
+ messagelikeme profile export CONTACT_ID --output FILE [--json]
583
+ messagelikeme context CONTACT_ID [--json]
584
+ messagelikeme handoff prepare CONTACT_ID --request FILE
585
+ --wrench-context FILE --draft FILE --output FILE [--json]
586
+ messagelikeme handoff verify FILE [--json]
587
+ messagelikeme handoff record HANDOFF_ID --wrench-receipt FILE [--json]
588
+ messagelikeme handoffs show HANDOFF_ID [--json]
589
+ messagelikeme skill path [--json]
590
+ messagelikeme skill install [--target codex|claude|agents]
591
+ [--scope user|project] [--project PATH] [--force] [--json]
592
+ messagelikeme doctor [--json]
593
+ ```
594
+
595
+ Place global `--data-dir PATH` before the command.
596
+
597
+ ## Privacy model
598
+
599
+ - The original `chat.db` and AddressBook databases remain authoritative.
600
+ SQLite opens only stable private copies, never the source files or sidecars.
601
+ - X data archives remain private caller-owned inputs. Import reads supported
602
+ entries directly from the ZIP without extraction, evaluation, network access,
603
+ or media download, and retains exact archive provenance.
604
+ - Source bundles remain private caller-owned inputs. Import verifies their
605
+ fixed inventory, canonical bytes, digests, bounds, and owner-only modes.
606
+ - The normalized corpus, profiles, and installation key stay in a private local
607
+ store with owner-only permissions.
608
+ - Stable source, contact, participant, conversation, message, and reaction IDs
609
+ are derived with a private per-install HMAC key. Pseudonymous IDs are not
610
+ encryption.
611
+ - Aggregate commands omit bodies and private labels. Study, Ensoul, evaluation,
612
+ and handoff packets are bounded, explicit body-bearing exports.
613
+ - Message text never goes to a Message Like Me server. There is no service,
614
+ account, auth flow, analytics client, or network-backed model call.
615
+ - Opening a study or Ensoul packet makes its bounded excerpts visible to the
616
+ agent environment already running the skill. Use an agent environment whose
617
+ data handling you accept; the CLI cannot make a hosted agent local.
618
+ - Public fixtures are synthetic. Private corpora, profiles, packets, and drafts
619
+ do not belong in Git, issues, logs, packages, or examples.
620
+ - A draft is never sent.
621
+
622
+ Read [SECURITY.md](SECURITY.md) before integrating the library into another
623
+ tool or handling a private packet outside the CLI. The
624
+ [methodology](docs/methodology.md) defines every unit and evidence boundary;
625
+ the [research review](docs/research.md) documents papers, neighboring OSS, and
626
+ the claims this project does not make.
627
+
628
+ ## TypeScript library
629
+
630
+ The package exports the versioned corpus, metrics, study-packet, and profile
631
+ types plus deterministic canonical JSON and SHA-256 helpers:
632
+
633
+ ```ts
634
+ import type { ContactMetrics, StyleProfileV2 } from "@hraness/message-like-me"
635
+ import { canonicalJson, sha256 } from "@hraness/message-like-me"
636
+ ```
637
+
638
+ The immutable Beeper v1 and native WhatsApp v2 local message bundle contracts
639
+ have separate dependency-free producer and consumer surfaces:
640
+
641
+ ```ts
642
+ import {
643
+ LOCAL_MESSAGE_BUNDLE_V1_ARTIFACTS,
644
+ LOCAL_MESSAGE_BUNDLE_V1_SOURCE_TRANSFORM_VERSION,
645
+ parseLocalMessageBundleV1Manifest,
646
+ parseLocalMessageBundleV1Record,
647
+ } from "@hraness/message-like-me/message-bundle-v1"
648
+
649
+ import {
650
+ LOCAL_MESSAGE_BUNDLE_V2_ARTIFACTS,
651
+ LOCAL_MESSAGE_BUNDLE_V2_PROVIDER_VERSION,
652
+ parseLocalMessageBundleV2Manifest,
653
+ parseLocalMessageBundleV2Record,
654
+ } from "@hraness/message-like-me/message-bundle-v2"
655
+ ```
656
+
657
+ Those subpaths own the exact readonly wire types, compatibility constants,
658
+ safety bounds, digest helpers, and pure strict parsers. Importing it performs no
659
+ filesystem or network work.
660
+
661
+ The Ensoul messages-source builder and readonly wire types have a separate
662
+ dependency-free subpath. The matching strict JSON Schema is bundled at
663
+ `schema/ensoul-messages-source-v1.schema.json`:
664
+
665
+ ```ts
666
+ import {
667
+ buildEnsoulMessagesSourcePacketV1,
668
+ ENSOUL_MESSAGES_SOURCE_V1_ADAPTER_ID,
669
+ } from "@hraness/message-like-me/ensoul-source-v1"
670
+ import type {
671
+ EnsoulMessagesSourcePacketV1,
672
+ } from "@hraness/message-like-me/ensoul-source-v1"
673
+ ```
674
+
675
+ The library does not start the CLI, inspect Messages, Contacts, or an X archive,
676
+ connect to a network, or send a draft merely because it is imported.
677
+
678
+ ## Development
679
+
680
+ ```sh
681
+ bun install --frozen-lockfile --ignore-scripts
682
+ bun run check
683
+ ```
684
+
685
+ Tests use synthetic Messages and AddressBook databases, synthetic X archive
686
+ ZIPs, and synthetic source bundles and conversations. Never add a real message,
687
+ handle, group title, attachment, contact record, private path, or derived
688
+ profile to a fixture.
689
+
690
+ The canonical repository is
691
+ [`hraness/message-like-me`](https://github.com/hraness/message-like-me).
692
+ The informational project page is
693
+ [`messagelikeme.com`](https://messagelikeme.com). The CLI does not connect to
694
+ the site, and the site never receives message or contact data.
695
+
696
+ ## License
697
+
698
+ MIT.