A local-first CLI and Agent Skill for studying private messaging history and drafting messages that sound like you.
Message Like Me turns caller-owned messaging history into deterministic local metrics, bounded evidence packets, reusable style profiles, and unsent drafts. The CLI owns ingestion, storage, measurement, and export. Its copied Message Like Me and Ensoul Agent Skills teach Codex, Claude, and other coding agents how to interpret those artifacts through the agent environment you already use.
The result is an inspectable evidence layer for relationship-aware drafting, not a model of your identity. Your current meaning, facts, and intent outrank historical style.
- Separate evidence from interpretation. The CLI produces deterministic metrics and versioned packets without a model integration. Semantic analysis happens only when you deliberately open an artifact in your own agent environment.
- Study each relationship in context. Outgoing messages supply style evidence; incoming messages supply the response context. Tempo, sessions, bubble shape, replies, and prose remain attributable to their source.
- Keep private history bounded. Read-only import uses stable local copies, ordinary views omit bodies and private labels, and body-bearing exports go only to explicit owner-selected paths.
- Stop at an inspectable draft. The installed skill can help draft text, but Message Like Me never authenticates with a messaging provider, operates a messaging app, or sends, reacts to, or schedules a message.
Message Like Me requires Bun 1.3.14 or newer. Install the immutable public release from GitHub, then install both bundled Agent Skills:
bun add --global github:hraness/message-like-me#v0.8.0
messagelikeme skill installStart a new agent session after installing the message-like-me and ensoul
skills. The command preflights and stages both copied directories before it
publishes either one; --force replaces both as one installation. The default
target is Codex at user scope. Other supported targets and project-local
installation are available explicitly:
messagelikeme skill install --target claude
messagelikeme skill install --target agents --scope project
messagelikeme skill pathMessage Like Me is distributed directly through GitHub and is not published to npm.
Initialize an empty private store, then inspect it without reading any messaging source:
messagelikeme init
messagelikeme doctor --jsonThe JSON response reports the initialized state, exact local paths, and store integrity checks. This is the shortest complete product check; no message body, contact, account, or provider credential is involved.
After you add a source, ordinary commands expose pseudonymous contacts, source health, conversation counts, sessions, response tempo, bubble sequences, reactions, and explicit-reply coverage. Study and Ensoul commands can then write bounded, body-bearing packets to paths you name. Profiles retain the exact corpus revision and packet digest they came from, so stale evidence fails visibly instead of being treated as current.
| Source | What Message Like Me reads | Boundary |
|---|---|---|
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
On macOS, the default store is:
~/Library/Application Support/Message Like Me/
The directory is private to the current user. It contains a local SQLite
database, stored profiles, and a private installation key used to derive
stable pseudonymous IDs. Study packets are written only to the explicit path
you choose. You can put the store elsewhere by placing
--data-dir /absolute/private/path before the command.
Import the current user's iMessage database:
messagelikeme ingest imessage --jsonThe default source is the current user's Messages chat.db. Use --database
only to name another caller-owned physical database:
messagelikeme ingest imessage --database /absolute/path/to/chat.db --jsonIngestion validates the source schema and ownership, makes a stable private
copy of the database and its transactional sidecars, and opens only that copy
with SQLite. It does not change Messages, chat.db, or its sidecars. macOS may
require permission for the terminal or agent host to read Messages data.
Import direct messages from a caller-owned X data archive ZIP:
messagelikeme ingest x-archive \
--input /absolute/private/path/x-data-archive.zip \
--jsonThe importer requires a normalized absolute path to an owner-only physical ZIP and reads only the supported archive entries. It does not extract the archive, evaluate its JavaScript wrappers, access X or another network, or download linked media. It stores exact archive and account provenance with the normalized source so a later audit can identify which local export supplied the evidence. The supported source is the archive's direct-message history. X Chat history is not included. Bounded reply and mention identity metadata from selected tweet entries may help associate provider user IDs with X handles or display names; tweet bodies never become messaging-style evidence.
X data archives do not expose whether a direct message used an explicit reply link. Those messages still contribute body, direction, ordering, tempo, and response-shape evidence, but reply observability is marked unavailable. They do not enter the explicit-reply ratio and are not treated as observed non-replies.
When the same X account is already present through a Beeper bundle, first find that source in the redacted inventory, then name it explicitly:
messagelikeme sources list --json
messagelikeme ingest x-archive \
--input /absolute/private/path/x-data-archive.zip \
--overlap-source <beeper-x-source-id> \
--json--overlap-source is not a fuzzy merge switch. Reconciliation proceeds only
for one-to-one direct conversations whose account identity, peer handle, and
overlapping message evidence match exactly. Both source provenances remain
inspectable, ambiguity or contradiction fails closed, and exact messages appear
once in analysis. Group DMs remain separate because the legacy archive does not
supply enough cross-provider sender proof for exact equivalence. Reimporting the
same or a later archive preserves proven deduplication; archive absence does not
delete retained history.
To study accounts connected through Beeper, install the currently verified
@hraness/wrench@0.16.1
package from npm, then use Wrench to create a new private Message Like Me
bundle:
bun add --global @hraness/wrench@0.16.1
wrench beeper export-message-like-me \
--auth <beeper-auth-id> \
--output /absolute/private/path/beeper-bundle \
--jsonThe optional --limit-chats, --limit-messages, and --max-participants
flags lower the export bounds. The output path must be a normalized absolute
path to a directory that does not already exist. Wrench v0.16.1 calls the
pinned official Beeper CLI 0.6.2 release
directly. It enumerates
the connected account realm, invokes export --no-attachments once per
account in deterministic order, and reports the account ordinal, elapsed-time
heartbeats, and cumulative validated chat and message counts on stderr. It
retains each private raw shard until it can atomically publish the complete
mode-0700 seven-file bundle with mode-0600 files.
The export does not use the separate Beeper Desktop API MCP project. The CLI path supplies the bounded account snapshots and local files needed for hash validation, deterministic conversion, crash recovery, and atomic publication. Provider URLs and credentials are excluded. Message Like Me does not receive the Beeper credential, start Wrench, invoke a Beeper operation, or send a message.
Ingest the finished directory, then inspect its redacted source health:
messagelikeme ingest bundle --input /absolute/private/path/beeper-bundle --json
messagelikeme sources list --json
messagelikeme sources show <source-id> --jsonThe importer verifies the fixed version-one inventory, canonical UTF-8 NDJSON, record and byte bounds, owner-only permissions, artifact digests, and manifest digest before changing the store. One bundle may contain several connected accounts and networks; each becomes a separate source namespace. Native iMessage and prior bundle sources remain alongside it.
The interchange, integrity, identity, and reimport laws are in the
version-one local message bundle contract.
Message Like Me accepts bundle schema 1 with source ID beeper-local and
source-transform version 1.1.0. Wrench v0.16.1 is the currently verified
producer. Compatibility is determined by those exact manifest coordinates,
not by an open-ended Wrench package range.
Beeper exports describe bounded local observations. A later bounded export
that omits an older record does not delete retained history. Explicit deletion,
removal, replacement, and tombstone records suppress their target, and a later
reappearance restores it. Older snapshots cannot overwrite newer state. Use
sources show <source-id> --private --json only when you deliberately need the
private provider account and source metadata.
For native WhatsApp evidence, install Wrench v0.16.3 and let its official Wacli 0.15.0 adapter create the one-account v2 bundle:
bun add --global @hraness/wrench@0.16.3
wrench whatsapp export-message-like-me \
--auth <whatsapp-auth-id> \
--output /absolute/private/path/whatsapp-bundle \
--json
messagelikeme ingest bundle \
--input /absolute/private/path/whatsapp-bundle \
--jsonMessage Like Me accepts exactly bundle schema 2, source
wacli-local@1.0.0, provider whatsapp@0.15.0, and network whatsapp. The
bundle admits canonical WhatsApp user, LID, and group JIDs; only an exact
E.164-backed user JID supplies a Contacts-match phone handle. Status,
broadcast, newsletter, credential, session-database, provider-URL, and media-byte
surfaces are excluded. The complete contract is in
local message bundle v2.
If a Beeper WhatsApp source already represents the same exact account, inspect the redacted source inventory and name it explicitly:
messagelikeme ingest bundle \
--input /absolute/private/path/whatsapp-bundle \
--overlap-source <beeper-whatsapp-source-id> \
--jsonReconciliation requires exact self and direct-peer E.164 identity plus an
unambiguous shared text-message fingerprint. Groups, bodyless messages, names,
phone suffixes, and approximate timestamps cannot prove equivalence. Both
provenances and source-unique history remain. Native Wacli evidence becomes the
preferred whatsappJid route; its proven Beeper duplicate remains
evidence-only with reason superseded-route.
Optionally enrich and join direct conversations with private identities from macOS Contacts:
messagelikeme ingest contacts --jsonThe default source is the current user's AddressBook directory. An explicit
absolute AddressBook root, Sources directory, store directory, or
AddressBook-vN.abcddb file can be selected with --addressbook:
messagelikeme ingest contacts \
--addressbook /absolute/path/to/AddressBook \
--jsonContacts ingest may run before or after any message source. It reads only
bounded name, email, and phone fields from a stable private copy. Exact
normalized email or E.164 phone handles can join several one-to-one threads
for the same AddressBook person into one analysis scope. An imported
conversation is eligible only when its source positively establishes a
complete direct participant roster. Existing conversation IDs remain aliases
for that person scope. Shared handles remain ambiguous, local phone numbers
never gain a guessed country code, unmatched threads stay separate, and groups
are never collapsed to one person. Contact labels have their own revision, so
a rename does not stale a messaging-style profile. messagelikeme doctor
reports local aggregate state without asking for an account or credential.
Contact listings and aggregate views omit private labels, handles, and message bodies by default:
messagelikeme contacts list --min-outgoing 20 --json
messagelikeme contacts show <contact-id> --json
messagelikeme inspect tempo <contact-id> --session-gap 28800 --burst-gap 300 --json
messagelikeme inspect sessions <contact-id> --limit 20 --jsonThe metrics cover conversation start and end, message counts, incoming and
outgoing turns, within-session response latency, single-message versus
multi-message replies, surface prose features, multi-point response contexts,
reactions, and explicit reply use. Incoming messages establish what you were
responding to; they are never counted as examples of your writing style.
Sessions, bursts, and response episodes never cross a source conversation
boundary. Person scopes spanning several apps expose a sorted services
breakdown instead of hiding the mixed-channel evidence behind a null service.
Reactions with no provider timestamp still contribute to reaction counts and
direction, but never to temporal metrics. Raw provider reaction values remain
private; ordinary metrics and drafting context expose only fixed-size counts,
direction, datedness, and the outgoing reaction ratio. Session and burst gaps
are configurable seconds and are recorded with each result. They are
segmentation choices, not universal facts about conversation.
Explicit-reply metrics also report how many outgoing text messages were eligible for reply measurement and how many came from a source where reply links were unavailable. The ratio uses only eligible messages. Do not compare an X archive's unavailable reply metadata with an observed no-reply decision from iMessage or a compatible bundle.
Pass --private to contacts list or contacts show only when you need to
resolve a pseudonymous contact to its local private label or participants.
When you already know the complete Contacts label, resolve only that exact private name instead of listing every label:
messagelikeme contacts resolve "Exact Contact Name" --private --jsonResolution is normalized for case and Unicode representation, but it does not perform prefix, substring, phonetic, or fuzzy matching. It returns only direct person scopes and labels, never handles or message bodies.
Message Like Me can bind an ordered unsent draft to one exact local source-conversation candidate and one current opaque Wrench context. It still does not invoke Wrench, authenticate, launch a provider command, access a network, or send a message.
Start with the redacted candidate inventory:
messagelikeme routes list <contact-id> \
--output /absolute/private/routes.json --jsonA contact or person scope is never itself a destination. Each candidate names
one pseudonymous source and conversation inside that mode-0600 output.
Ordinary stdout reports only its digest, counts, and selection state.
--private additionally reveals only
the exact account, source, and tagged conversation coordinate already observed
in that imported source: beeperConversation for a Beeper bundle,
whatsappJid for a native Wacli bundle, or imessageChat for Messages. It never
emits names, handles, participants, or a locator derived from them. Wrench
rejects a coordinate whose tag does not match the selected provider adapter.
An X archive candidate is always evidence-only with reason
archive-source. Handoff v1 also keeps group candidates evidence-only as an
explicit direct-conversation product limit. This does not claim that Wrench or
a provider cannot address an exact group. It prevents Message Like Me from
authorizing one through this first handoff contract. Several eligible direct
candidates produce an ambiguous selection state; no route is chosen from a
name, title, participant list, or merged person scope.
After Wrench has written its exact current context and the agent has written an ordered one-to-eight-bubble draft, prepare one private handoff:
messagelikeme handoff prepare <contact-id> \
--request /absolute/private/handoff-request.json \
--wrench-context /absolute/private/wrench-context.json \
--draft /absolute/private/draft.json \
--output /absolute/private/handoff.json \
--jsonThe request contains the exact selected source-conversation candidate from the
private route inventory. The request, context, and draft must be owner-only,
singly linked physical files. The context must carry the pinned
wrench.messaging-context-binding.v1 contract identity,
an unexpired opaque route and context reference, and exact SHA-256 data and
latest-message revisions. The output is a mode-0600 file whose canonical
digest binds those values, the selected source revision, corpus and profile
evidence, bubble order, text, and optional reply references. Raw opaque Wrench
references and draft bodies appear only in the explicit private inputs and
handoff file.
Verification and audit commands emit hashes, counts, timestamps, and pseudonymous IDs without bodies or raw Wrench references:
messagelikeme handoff verify /absolute/private/handoff.json --json
messagelikeme handoff record <handoff-id> \
--wrench-receipt /absolute/private/wrench-receipt.json --json
messagelikeme handoffs show <handoff-id> --jsonRecording requires Wrench's pinned body-free receipt binding. It carries the
provider-neutral client-intent digest, set to this exact Message Like Me
handoff digest, along with route-reference, context-reference, exact-turn, and
private-preview digests. It also carries the proven prefix and a canonical
receipt digest. It contains no raw route or context reference. Message Like Me
stores only those hashes, counts, timestamps, states, and pseudonymous run and
handoff IDs. It never
inserts a sent message into the corpus. A later independent source ingestion
must observe that message before it can affect
style, tempo, reply, or interaction evidence. The pure contract is exported as
@hraness/message-like-me/agentic-messaging-v1 for checked local consumers.
Message Like Me can prepare one private messages-source packet for the local owner or for one exact direct person resolved through Contacts:
messagelikeme ensoul prepare <contact-id> \
--subject owner \
--output /absolute/private/path/owner-messages.json \
--after 2026-01-01T00:00:00.000Z \
--before 2026-08-01T00:00:00.000Z \
--limit 24 \
--json
messagelikeme ensoul prepare <person-id> \
--subject contact \
--output /absolute/private/path/contact-messages.json \
--limit 24 \
--jsonFor --subject contact, <person-id> must be the exact person_… result of
contacts resolve; a conversation alias, unmatched direct thread, or group is
rejected. Both subject modes reject groups and multi-participant scopes. The
packet carries only redacted scope shape, conversation count, and sorted service
labels; private names, participants, and provider coordinates remain excluded.
The adapter reverses owner-relative direction before selection, so a
contact's attributable incoming prose becomes authorRole: subject and the
owner's prose becomes counterpart. For --subject owner, outgoing prose is
the subject's and incoming prose is counterpart context. A consumer must never
learn counterpart text as the subject's voice.
The output is a strict ensoul.source-packet.v1 artifact with payload schema
ensoul.messages-source.v1. It reuses the deterministic diverse response
selector and the same default limits as a study packet: 24 response examples,
4 KiB per text body, 12 messages per direction per example, and 256 KiB total.
It excludes system events, retractions, reactions, attachments, names, handles,
and raw provider coordinates. Scope revisions, inclusive --after, exclusive
--before, selection omissions, byte truncation, transport status, strong
source authorship, content and record digests, and the RFC 8785 canonical packet
digest remain explicit. The scope and body-free receipt also preserve the exact
session and burst gaps used for selection, including defaults. Selected prose
records use contentRole: original. Records sharing one pseudonymous
provenance.runId belong to the same selected
response context. Consumers must not join records from different context IDs.
The normalized sources cannot detect pasted quotations, forwarding, or AI
assistance; the packet states that limitation, and visible quoted material must
remain contextual. No claims are derived in the adapter.
The packet is written once to an explicit owner-controlled mode-0600 physical
path; it never appears on stdout, and an existing file or symlink is not
overwritten. The JSON receipt contains only pseudonymous IDs, revisions,
digests, counts, bounds, and the chosen path. Run $ensoul in an agent
environment you authorize, open only that packet, and preserve its limitations.
Private messages are situated evidence, not a transcript, identity proof,
consent record, relationship label, diagnosis, stable personality, or authority
to impersonate or act for anyone.
Aggregate metrics cannot explain why a short burst works in one context or why a longer single message appears in another. For that semantic work, prepare a small, diverse study packet at an explicit private path:
messagelikeme study prepare <contact-id> \
--output /absolute/private/path/study.json \
--before 2026-08-01T00:00:00.000Z \
--limit 24 \
--jsonstudy prepare, ensoul prepare, evaluate prepare, and handoff prepare are
the only commands that write bounded message bodies outside the private
database. Their outputs are mode 0600.
A study packet contains incoming context and outgoing responses selected across
different response shapes; it is not a full transcript export. By default,
each body is capped at 4 KiB, each example keeps at most 12 text messages per
direction, and the entire packet keeps at most 256 KiB of body text. Packet
coverage fields report every truncation or omission explicitly.
Keep the JSON receipt with the analysis. Its packetSha256 binds the finished
profile to these exact packet bytes; the packet does not contain its own digest.
--after is inclusive and --before is exclusive. Temporal bounds let you
reserve later conversations for evaluation. Invoke $message-like-me in your
agent and ask it to analyze that contact. The skill separates measured facts
from inferred patterns, covers prose and tempo, studies how several inbound
points are handled, and treats reply links and tapbacks separately from written
text.
The agent writes a schema-version-two profile and asks the CLI to validate and store it:
messagelikeme profile apply /absolute/private/path/profile.json --json
messagelikeme profile show <contact-id> --jsonA version-two profile records the global corpus revision for provenance, a person-and-window-specific evidence revision for validity, the exact study-packet SHA-256, and the packet's non-body evidence manifest. Measured and inferred claims cite valid packet example IDs and record counterexamples, support counts, confidence, and drafting consequences. Messages for someone else or outside the studied time window do not stale it; changes inside its actual evidence do.
Export a profile only when you need an explicit private copy:
messagelikeme profile export <contact-id> --output /absolute/private/path/profile.jsonVersion-one profiles remain readable for migration, but new analyses should use
schema/style-profile-v2.schema.json.
Prepare a separate prompt and reference set from conversations after the study cutoff:
messagelikeme evaluate prepare <contact-id> \
--after 2026-08-01T00:00:00.000Z \
--prompt-output /absolute/private/path/evaluation-prompts.json \
--reference-output /absolute/private/path/evaluation-references.json \
--jsonGive the agent only the prompt file and fix one candidate bubble sequence per case before opening the reference file. Then compare intent coverage, factual meaning, prose, bubble shape, explicit replies, privacy leakage, and calibration. The files support a blind workflow but do not enforce one, and the historical response is one observation rather than a unique correct answer. The CLI deliberately does not collapse these dimensions into a universal fidelity score. See the methodology.
Ask an agent with the installed $message-like-me skill to draft for a
pseudonymous contact. The compact deterministic context is available through:
messagelikeme context <contact-id> --jsonThe skill preserves your intended meaning, selects the applicable profile, and can express the result as one message or a realistic sequence of separate bubbles. It uses explicit replies only when your evidence and the current context support them.
Drafting ends with text in the agent task. Message Like Me has no send, react, schedule, or messaging-application command.
- Install and prove the local boundary: use Install and first run.
- Add evidence: choose a path under Supported sources, then follow Add private local history.
- Study or draft: inspect behavior, prepare a study packet, build a profile, audit it against later conversations, then draft an unsent reply using the task sections above.
- Review evidence and safety claims: read the methodology, research review, and security policy.
- Integrate a producer: use the versioned Beeper bundle, WhatsApp bundle, and public JSON Schemas instead of inferring a wire format from examples.
Run messagelikeme --help for the checked grammar. The public surfaces are:
messagelikeme init [--json]
messagelikeme ingest imessage [--database PATH] [--json]
messagelikeme ingest x-archive --input ABS_PATH
[--overlap-source SOURCE_ID] [--json]
messagelikeme ingest contacts [--addressbook PATH] [--json]
messagelikeme ingest bundle --input ABS_PATH
[--overlap-source SOURCE_ID] [--json]
messagelikeme sources list [--private] [--json]
messagelikeme sources show SOURCE_ID [--private] [--json]
messagelikeme contacts list [--min-outgoing N] [--limit N] [--private] [--json]
messagelikeme contacts show CONTACT_ID [--private] [--json]
messagelikeme contacts resolve QUERY --private [--limit N] [--json]
messagelikeme routes list CONTACT_ID --output FILE [--private] [--json]
messagelikeme inspect tempo CONTACT_ID [--session-gap N] [--burst-gap N] [--json]
messagelikeme inspect sessions CONTACT_ID [--limit N] [--session-gap N] [--burst-gap N] [--json]
messagelikeme study prepare CONTACT_ID --output FILE [--limit N]
[--after ISO_TIMESTAMP] [--before ISO_TIMESTAMP]
[--session-gap N] [--burst-gap N] [--json]
messagelikeme ensoul prepare CONTACT_ID --subject owner|contact --output FILE
[--limit N] [--after ISO_TIMESTAMP] [--before ISO_TIMESTAMP]
[--session-gap N] [--burst-gap N] [--json]
messagelikeme evaluate prepare CONTACT_ID --after ISO_TIMESTAMP
--prompt-output FILE --reference-output FILE [--before ISO_TIMESTAMP]
[--limit N] [--session-gap N] [--burst-gap N] [--json]
messagelikeme profile apply FILE [--json]
messagelikeme profile show CONTACT_ID [--json]
messagelikeme profile export CONTACT_ID --output FILE [--json]
messagelikeme context CONTACT_ID [--json]
messagelikeme handoff prepare CONTACT_ID --request FILE
--wrench-context FILE --draft FILE --output FILE [--json]
messagelikeme handoff verify FILE [--json]
messagelikeme handoff record HANDOFF_ID --wrench-receipt FILE [--json]
messagelikeme handoffs show HANDOFF_ID [--json]
messagelikeme skill path [--json]
messagelikeme skill install [--target codex|claude|agents]
[--scope user|project] [--project PATH] [--force] [--json]
messagelikeme doctor [--json]
Place global --data-dir PATH before the command.
- The original
chat.dband AddressBook databases remain authoritative. SQLite opens only stable private copies, never the source files or sidecars. - X data archives remain private caller-owned inputs. Import reads supported entries directly from the ZIP without extraction, evaluation, network access, or media download, and retains exact archive provenance.
- Source bundles remain private caller-owned inputs. Import verifies their fixed inventory, canonical bytes, digests, bounds, and owner-only modes.
- The normalized corpus, profiles, and installation key stay in a private local store with owner-only permissions.
- Stable source, contact, participant, conversation, message, and reaction IDs are derived with a private per-install HMAC key. Pseudonymous IDs are not encryption.
- Aggregate commands omit bodies and private labels. Study, Ensoul, evaluation, and handoff packets are bounded, explicit body-bearing exports.
- Message text never goes to a Message Like Me server. There is no service, account, auth flow, analytics client, or network-backed model call.
- Opening a study or Ensoul packet makes its bounded excerpts visible to the agent environment already running the skill. Use an agent environment whose data handling you accept; the CLI cannot make a hosted agent local.
- Public fixtures are synthetic. Private corpora, profiles, packets, and drafts do not belong in Git, issues, logs, packages, or examples.
- Message Like Me does not train a model, represent your identity, infer your beliefs, or claim that a draft is what you would have written. Current facts, meaning, and intent remain the user's responsibility.
- A draft is never sent.
Read SECURITY.md before integrating the library into another tool or handling a private packet outside the CLI. The methodology defines every unit and evidence boundary; the research review documents papers, neighboring OSS, and the claims this project does not make.
The package exports the versioned corpus, metrics, study-packet, and profile types plus deterministic canonical JSON and SHA-256 helpers:
import type { ContactMetrics, StyleProfileV2 } from "@hraness/message-like-me"
import { canonicalJson, sha256 } from "@hraness/message-like-me"The immutable Beeper v1 and native WhatsApp v2 local message bundle contracts have separate dependency-free producer and consumer surfaces:
import {
LOCAL_MESSAGE_BUNDLE_V1_ARTIFACTS,
LOCAL_MESSAGE_BUNDLE_V1_SOURCE_TRANSFORM_VERSION,
parseLocalMessageBundleV1Manifest,
parseLocalMessageBundleV1Record,
} from "@hraness/message-like-me/message-bundle-v1"
import {
LOCAL_MESSAGE_BUNDLE_V2_ARTIFACTS,
LOCAL_MESSAGE_BUNDLE_V2_PROVIDER_VERSION,
parseLocalMessageBundleV2Manifest,
parseLocalMessageBundleV2Record,
} from "@hraness/message-like-me/message-bundle-v2"Those subpaths own the exact readonly wire types, compatibility constants, safety bounds, digest helpers, and pure strict parsers. Importing it performs no filesystem or network work.
The Ensoul messages-source builder and readonly wire types have a separate
dependency-free subpath. The matching strict JSON Schema is bundled at
schema/ensoul-messages-source-v1.schema.json:
import {
buildEnsoulMessagesSourcePacketV1,
ENSOUL_MESSAGES_SOURCE_V1_ADAPTER_ID,
} from "@hraness/message-like-me/ensoul-source-v1"
import type {
EnsoulMessagesSourcePacketV1,
} from "@hraness/message-like-me/ensoul-source-v1"The library does not start the CLI, inspect Messages, Contacts, or an X archive, connect to a network, or send a draft merely because it is imported.
bun install --frozen-lockfile --ignore-scripts
bun run checkThe complete gate type-checks the project, runs the synthetic test suite,
validates both bundled Agent Skills and the standalone public boundary,
rebuilds dist/, checks the committed build, and exercises a packed consumer.
Tests use synthetic Messages and AddressBook databases, synthetic X archive ZIPs, and synthetic source bundles and conversations. Never add a real message, handle, group title, attachment, contact record, private path, or derived profile to a fixture.
Read CONTRIBUTING.md before opening a change.
The canonical repository is
hraness/message-like-me.
The informational project page is
messagelikeme.com. The CLI does not connect to
the site, and the site never receives message or contact data.
MIT.