@hraness/message-like-me 0.8.9 → 0.8.11

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.
@@ -1,5 +1,99 @@
1
1
  # Publish Message Like Me
2
2
 
3
+ ## Textbutler informational-site delivery
4
+
5
+ The following explicit site-subject route supersedes the package-publication
6
+ prerequisite below for the informational Textbutler website only. It grants no
7
+ package publication, native app release, provider qualification, or live message
8
+ authority. The legacy tagged package route and its exact-byte/npm checks remain
9
+ unchanged. Source repository identity is the canonical `hraness/textbutler`
10
+ with unchanged numeric repository ID `1342143606`.
11
+
12
+ Use the existing `Promote website production` workflow with `site_sha` set to
13
+ the exact reviewed current `main` commit, `site_ci_run_id` and
14
+ `site_ci_run_attempt` set to its successful `CI` run, and `release_tag` empty.
15
+ Do not rerun a promotion attempt; make a fresh attempt-1 dispatch. The route
16
+ requires exactly the current standalone/site job and macOS fixture/native job,
17
+ rejects an older attempt after CI is rerun, rebuilds the site, and admits a
18
+ bounded immutable Actions build-manifest artifact by exact run, attempt, source
19
+ tree, site subtree, lockfile digest, artifact ID/digest, and manifest bytes.
20
+
21
+ An advancing site target uses the same `website-production` ref, shared
22
+ promotion concurrency, existing status-only App, environment, rulesets, and
23
+ ordinary GitHub Actions ref writer as the legacy route. Before the App key is
24
+ available, complete governed Git histories and all ordered workflow-tree
25
+ changes must pass the site control-epoch admission. A changed workflow range
26
+ requires the independently reviewed exact digest emitted by preflight in
27
+ `control_epoch_digest`; unchanged ranges reject a supplied digest. Site epochs
28
+ use domain `textbutler/control-epoch/site/v1`, bind target and workflow source to
29
+ the same current `main` commit, and carry `tag: null`. They cannot authorize a
30
+ legacy package release. Both routes retain checked helper hashes and code-owner
31
+ review for the complete authority implementation.
32
+
33
+ Preserve the environment's actual protection rules. If the existing key
34
+ environment requires a reviewer, satisfy that exact run's review through
35
+ GitHub's normal environment approval interface after source admission and
36
+ independent control review. The site redesign does not remove reviewers, wait
37
+ timers or other runtime gates; the legacy setup policy below is not permission
38
+ to bypass an existing protection.
39
+
40
+ The site writer consumes prior status, proves that the ordinary writer is
41
+ denied by the exact required App status, attests one target, revokes the App
42
+ token, verifies current rules/status/source again, and makes one sterile
43
+ fast-forward push with the expected-old lease. App-only steps receive no ref
44
+ token; writer/admission steps receive no App key. The terminal consumption step
45
+ runs even after failure or cancellation when admission completed. The final
46
+ read-only jobs verify status consumption, revocations, unchanged rules, and
47
+ twice-confirmed REST/GraphQL Vercel Production identity for the exact source.
48
+
49
+ `textbutler-site-attempt` retains the explicitly listed public phase receipts
50
+ for 30 days when the runner can upload them. Hard termination may prevent this
51
+ upload; remote ref/status/provider readback and the existing interrupted
52
+ authority cleanup remain authoritative. Never blindly repeat an uncertain
53
+ write. If the ref already equals the site source, a fresh dispatch takes a
54
+ read-only qualification route with no App key or ref token. It requires the
55
+ status already consumed by the exact App bot (including its numeric actor
56
+ identity), the unchanged App-pinned rules, and the matching Production
57
+ deployment after the original successful CI completion. The retry's newly
58
+ built manifest is not treated as that existing deployment's publication time.
59
+ Leave `control_epoch_digest` empty for this already-exact route. An outstanding
60
+ success status must first pass the documented custody cleanup, not be silently
61
+ cleared by read-only qualification.
62
+
63
+ After provider success, verify `textbutler.app` serves the exact intended
64
+ deployment, truthful development status, canonical metadata and legacy links.
65
+ The current Vercel project/production branch remain the target; domain or
66
+ repository renaming is a separate inspected provider mutation.
67
+
68
+ ## Repository identity migration
69
+
70
+ The repository rename keeps numeric GitHub repository ID `1342143606` and the
71
+ existing GitHub Apps, rulesets, protected refs, status contexts, environment
72
+ reviewers, workflow IDs, Vercel project, and production branch unchanged. The
73
+ canonical source identity for new release and promotion runs is
74
+ `hraness/textbutler`; an old-name redirect is not authority for a new run.
75
+ The npm names `@hraness/message-like-me` and `@hraness/agentrouter`, their tag
76
+ namespaces, the `messagelikeme` command, wire schemas, and historical release
77
+ receipts remain unchanged. Do not rewrite an existing tag, npm version, or
78
+ provenance statement.
79
+
80
+ Merge this version-neutral control migration independently before the next
81
+ product/version change. Refresh the complete administrative controls census
82
+ and independently review the exact helper/workflow changes. Before tagging,
83
+ read back each applicable npm trusted publisher and require canonical repository
84
+ `hraness/textbutler`, its existing exact workflow filename and permission set.
85
+ An old publisher identity blocks publication; do not fall back to a personal
86
+ token or weaken its policy. Any needed provider reconciliation is a separate
87
+ inspected operation. Historical versions retain their original provenance and
88
+ are not evidence for a new canonical-repository publication.
89
+
90
+ Before production promotion, follow the existing no-digest preflight and exact
91
+ reviewed control-epoch transition below. Preserve the actual key-environment
92
+ review and satisfy it through GitHub's normal interface. Neither the rename nor
93
+ this source migration permits an out-of-band ref move or a protection change.
94
+
95
+ ## Legacy package publication
96
+
3
97
  Message Like Me builds one exact public package tarball, validates those bytes
4
98
  on macOS and Linux, and publishes the same tarball plus `SHA256SUMS` to an
5
99
  immutable GitHub Release. Only then does it publish that tarball to npm through
@@ -20,6 +114,33 @@ denial, expected-old lease, and provider readback are machine gates. An agent
20
114
  may perform the independent review and exact dispatch required for a changed
21
115
  workflow-control epoch; that review precedes dispatch.
22
116
 
117
+ ## AgentRouter package publication
118
+
119
+ `@hraness/agentrouter` publishes through its own tag namespace and its own
120
+ tag-triggered workflow, `release-agentrouter.yml`, mirroring the root Release
121
+ contract. An annotated `agentrouter-v<version>` tag on a reviewed `main`
122
+ ancestor runs the same verify → exact-artifact → immutable GitHub Release →
123
+ read-only retry admission → OIDC-only npm writer → final public admission
124
+ chain, with the AgentRouter package manifest, tarball name, tag prefix, and
125
+ workflow path bound through the shared closed release-package descriptor. Both
126
+ release workflows share concurrency group `stable-release`, so a root and an
127
+ AgentRouter release can never interleave their GitHub Latest assertions. The
128
+ AgentRouter channel has no site or production-promotion coupling; its releases
129
+ distribute the package only.
130
+
131
+ Owner-side controls required once before the first AgentRouter tag:
132
+
133
+ - Extend the immutable-tag ruleset coverage to `refs/tags/agentrouter-v*` with
134
+ the same update and deletion restrictions and no bypass actors as the
135
+ existing `refs/tags/v*` scope.
136
+ - Ensure `@hraness/agentrouter` exists publicly under the Hraness npm scope,
137
+ then configure its sole trusted publisher as GitHub Actions repository
138
+ `hraness/textbutler`, workflow file `release-agentrouter.yml`. Require
139
+ the exact permission set `createPackage` plus npm's provider-imposed
140
+ `createStagedPackage`, and keep the staged-package inventory exactly empty.
141
+ Once trusted publishing is proven, disallow traditional token publication
142
+ for the package.
143
+
23
144
  ## Establish the production controls once
24
145
 
25
146
  Apply these controls in order. Record the exact readbacks in the change review.
@@ -59,7 +180,7 @@ for this rollout and do not create a replacement Sites project.
59
180
  production authorization. Give the App exactly repository permissions
60
181
  `Commit statuses: Read and write` and implicit `Metadata: Read`, with no
61
182
  organization permission. Install it on `hraness` with selected-repository
62
- access to exactly `hraness/message-like-me`. Record its numeric App ID,
183
+ access to exactly `hraness/textbutler`. Record its numeric App ID,
63
184
  client ID, numeric installation ID, App slug, and the repository's numeric
64
185
  ID `1342143606`. These are distinct identities. Read the repository ID from
65
186
  GitHub's authenticated repository API and do not substitute a name at the
@@ -107,7 +228,7 @@ for this rollout and do not create a replacement Sites project.
107
228
  published Release reports `immutable=true` before npm can run.
108
229
  10. Ensure `@hraness/message-like-me` exists publicly under the Hraness npm
109
230
  scope, then configure its sole trusted publisher as GitHub Actions repository
110
- `hraness/message-like-me`, workflow file `release.yml`. Require its exact
231
+ `hraness/textbutler`, workflow file `release.yml`. Require its exact
111
232
  permission set to be `createPackage` plus npm's provider-imposed
112
233
  `createStagedPackage`. The checked Release workflow uses only its reviewed
113
234
  direct `npm publish` path, never `npm stage` or `stage publish`, and release
@@ -156,7 +277,7 @@ assertions together:
156
277
  `hraness`, exactly `statuses:write` plus `metadata:read`, no `contents` or
157
278
  `workflows` authority, and an exhaustive
158
279
  `/installation/repositories` set of exactly
159
- `{hraness/message-like-me}` with repository ID `1342143606`;
280
+ `{hraness/textbutler}` with repository ID `1342143606`;
160
281
  - `production-ref-writer-key` admits only `main`, has no required reviewers,
161
282
  wait timer, or custom deployment-protection rules, disables administrator
162
283
  bypass, and exposes only the expected key and checked variables;
@@ -273,7 +394,7 @@ When an established protected ref predates reviewed workflow-control changes:
273
394
  mode: process.env.MODE,
274
395
  previousSha: process.env.PREVIOUS_SHA,
275
396
  protectedRef: process.env.PROTECTED_REF,
276
- repository: "hraness/message-like-me",
397
+ repository: "hraness/textbutler",
277
398
  repositoryId: 1342143606,
278
399
  tag: process.env.VERIFIED_TAG,
279
400
  targetSha: process.env.TARGET_SHA,
@@ -322,7 +443,7 @@ When an established protected ref predates reviewed workflow-control changes:
322
443
  treat that target's `.github/workflows` tree OID as the baseline for the next
323
444
  routine range. Re-read the permanent App's exact `statuses:write` plus
324
445
  `metadata:read` permissions, absence of `contents` and `workflows` authority,
325
- singleton `{hraness/message-like-me}` repository selection, and the terminal
446
+ singleton `{hraness/textbutler}` repository selection, and the terminal
326
447
  non-success status. A completed epoch requires no key rotation because it
327
448
  created or replaced no credential and every short-lived App token was revoked;
328
449
  an interrupted run still follows the separate quarantine and cleanup
@@ -629,7 +750,7 @@ missed slot is skipped rather than retried or shifted, and request, body, and
629
750
  sleep latency all consume the same window. App identity, installation, mint,
630
751
  DELETE, and observation bodies are streamed under a 1 MiB cap and scrubbed
631
752
  after parsing. Every HTTP 200 must still describe the exact singleton selected
632
- `hraness/message-like-me` repository with ID `1342143606`. Acceptance requires
753
+ `hraness/textbutler` repository with ID `1342143606`. Acceptance requires
633
754
  two distinct scheduled HTTP 401 authorization-denial reads. An HTTP 403 is
634
755
  indeterminate because GitHub can use it for rate limiting or policy denial; it
635
756
  never proves revocation. A 200 after either denial, only one denial, any other
@@ -0,0 +1,28 @@
1
+ # Shared support protocol notice
2
+
3
+ The CLI includes `@hraness/support-foundation` 0.3.0 from reviewed commit
4
+ `2d034b357680353574411217d68b02b6755b07ed` of
5
+ [Hraness Support Foundation](https://github.com/hraness/support-foundation).
6
+ Its code is bundled only for the command-line support flow; public SDK exports do not include it.
7
+
8
+ MIT License
9
+
10
+ Copyright (c) 2026 Hraness
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
@@ -0,0 +1,154 @@
1
+ # Textbutler architecture
2
+
3
+ Textbutler is a personal message butler for macOS. An owner activates a bounded set of contacts. Each contact gets a private workspace that a coding agent can read and evolve. A separate daemon decides when to invoke that agent and controls every outward action.
4
+
5
+ The source includes the owner daemon, contact reply loop, versioned Ghostget automation protocol and macOS menu companion. Synthetic tests establish their control and recovery behavior. Live provider delivery has separate acceptance requirements below; the companion is a CLI artifact and has no signing or notarization gate.
6
+
7
+ ## Ownership
8
+
9
+ ```mermaid
10
+ flowchart LR
11
+ Menu[Menu companion] --> Control[Owner-only control socket]
12
+ Control --> Butler[Textbutler daemon]
13
+ Butler --> Router[Agentrouter]
14
+ Router --> Provider[Admitted agent provider]
15
+ Provider --> Tools[Contact-bound tool broker]
16
+ Tools --> Memory[One contact workspace]
17
+ Tools --> Web[Public web broker]
18
+ Tools --> Intents[Proposed actions]
19
+ Intents --> Butler
20
+ Butler --> Transport[Versioned transport adapter]
21
+ Transport --> Ghostget[Ghostget]
22
+ Ghostget --> Messages[iMessage and WhatsApp]
23
+ ```
24
+
25
+ Ghostget owns native permissions, message and contact acquisition, provider actions, and receipts. Textbutler does not open chat.db or automate Messages directly. Agentrouter owns provider selection, shared-account leases, cancellation, model catalogs, qualification evidence, and a bounded tool interface. Textbutler owns conversation policy and the durable send transaction. The menu companion changes owner settings through a small local protocol; it never gives a model arbitrary local commands.
26
+
27
+ The background lifecycle uses a user LaunchAgent: native messaging belongs to the signed-in Mac user, and login persistence is independent of the menu companion. Installation records the exact runtime, entrypoint, data directory and generation; removal verifies its private receipt and loaded service identity. Uninstall preserves contact data. New settings start paused. A private SQLite custody lock prevents duplicate daemon ownership and permits recovery only for a dead recorded process and its exact unserved socket. A temporary live launchd install/start/uninstall test passed while preserving synthetic owner data. The CLI companion is the supported release path; desktop app packaging has been removed; the CLI companion is the only local runtime surface.
28
+
29
+ The daemon serves owner controls, enrollment jobs and a reply loop through its own supervised Ghostget process. Startup, enablement and recovery establish a silent event boundary, refresh bounded history and admit only subsequent eligible inbound messages. A failed catch-up pauses the affected conversation. No provider work starts without explicit owner account configuration.
30
+
31
+ ## Contact data
32
+
33
+ The intended application-support tree is:
34
+
35
+ ```text
36
+ Textbutler/
37
+ daemon.sock owner-only native control socket
38
+ state/
39
+ settings.json owner configuration and contact bindings
40
+ host.json optional private installed Ghostget CLI/account binding
41
+ runs.sqlite private run and send journal
42
+ daemon-custody.sqlite exclusive process/socket ownership
43
+ launch-agent-custody.sqlite lifecycle serialization
44
+ launch-agent.json exact LaunchAgent installation receipt
45
+ ghostget-automation-custody.json exact supervised messaging process claim
46
+ plugins/
47
+ extensions.json explicit owner-installed hook manifest
48
+ quiet-hours.ts example trusted executable extension
49
+ contacts/
50
+ <opaque-contact-id>/ the only model-visible workspace for one run
51
+ AGENTS.md editable response guidance; never authority
52
+ ABOUT.md relationship context and owner instructions
53
+ MEMORY.md concise, dated, source-attributed working notes
54
+ STYLE.md owner-style evidence and response preferences
55
+ history/ bounded, attributed conversation excerpts
56
+ notes/ task-specific notes and outstanding questions
57
+ attachments/ broker-admitted incoming files
58
+ outbox/ files proposed for this conversation
59
+ ```
60
+
61
+ Directory names use opaque identifiers, not contact names or phone numbers. Files are private to the Mac user. Models cannot traverse parent paths, links, other contact folders, or host configuration. The file broker supports reads, conditional writes, and exact edits. It exposes no symlink, directory, delete-tree, shell, or executable permission operations. Atomic replacement preserves the preceding file if a write fails. A stale memory revision produces a conflict instead of overwriting an owner's correction.
62
+
63
+ The owner chooses one verified direct conversation from a bounded Ghostget list. Enrollment rechecks the account incarnation and participant identity and creates a disabled contact. History import is a separate opt-in, limited to 200 recent, explicitly scoped messages, with message ID, time, and author preserved. The import records shortening and omissions; it does not fetch media. These records are context only, and historical automation may be unobservable. A later qualified initialization run may summarize preferences, conversational style, open tasks, and useful context into memory. It must distinguish evidence from inference and retain uncertainty. Later runs correct outdated notes and record sources. Proven butler output never becomes owner-style training evidence. No global person model or cross-contact retrieval is supplied by default.
64
+
65
+ The original Message Like Me corpus and profile tools remain an optional bounded bootstrap source. They do not become the live message transport. Old databases are not reset or silently migrated. There is no need to carry every previous archive/source feature into the menu companion.
66
+
67
+ ## Reply admission
68
+
69
+ Default contact mode is smart, but a new contact starts disabled. Activating more than the configured limit fails atomically; no existing contact is displaced. The initial limit is five, with owner settings from one to fifty.
70
+
71
+ An inbound event must identify one activated direct conversation. Historical, outgoing, butler-authored, unknown-author, group, reaction-only, and delivery events do not start reply runs. Persisted event identity prevents a duplicate send. One run may own a contact at a time.
72
+
73
+ The daemon waits eight seconds after an incoming message to collect a burst. Newer messages supersede older candidates. Owner typing suppresses a reply when that signal exists. Any recent owner message causes a five-minute cooldown. The runtime checks current messages and settings again after composition. Disabling a contact or pressing global pause cancels pending runs and invalidates their grants.
74
+
75
+ A whole-word, case-insensitive `butler` invocation permits a response after those deterministic gates. In keyword mode, other messages stay silent. In smart mode, a tool-free cheap model returns a strict structured classification. It should answer useful assistance requests, and stay silent during ordinary conversation, acknowledgments, emotional exchanges, or uncertain intent. Confidence below 0.85 stays silent. Malformed results, exhausted accounts, stale model catalogs, or timeouts never escalate to a more expensive agent automatically.
76
+
77
+ Classifier choice comes from the selected provider's fresh available-model catalog, with explicit cost metadata and structured-output capability. No permanently hard-coded "cheap" model alias is assumed. An owner can pin a classifier or reply model after availability validation. Classifier and responder share the chosen provider/account policy; one classifier gets no tool authority.
78
+
79
+ No typing signal can prove the owner is absent. The current adapters expose no typing signal; the daemon retains its message-based cooldown and final revision checks. The selected provider/account must independently qualify before smart mode can invoke a model.
80
+
81
+ ## Send transaction and disclosure
82
+
83
+ The model proposes actions. It does not dispatch them. Owner-installed hooks may shape the work or veto a reply, but all action validation, contact binding, limits, and disclosure run afterwards.
84
+
85
+ Every text action is wrapped by trusted code with the contact's three symbols. Each field must be one visible grapheme; an empty or invisible disclosure is rejected. Default rendering is `🤖{ hello this is my response }`.
86
+
87
+ Reactions, stickers, link previews, and app cards cannot literally carry that text prefix. A disclosed text companion is therefore the first action in a nontext response, and counts toward the eight-action maximum. The provider must execute in order and stop if that companion fails. App-specific cards may additionally identify Textbutler in their content, but never remove the companion requirement.
88
+
89
+ Before send, the runtime rechecks owner activity, conversation revision, current settings, capability availability, cancellation, attachment ownership, target message membership, and the contact grant. It asks the transport to prepare an exact plan with an expiry and digest. Immediately before submission, it journals the dispatch intent. The transport must atomically validate the grant, contact route, context revision, and plan digest at its own effect boundary.
90
+
91
+ `submitted` is not `delivered`. Partial and indeterminate outcomes pause further automated activity for that contact until explicit reconciliation. A crash while dispatching becomes indeterminate on recovery; it never causes a blind retry. A crash before dispatch abandons the run without sending. Provider failover cannot replay a possibly submitted action.
92
+
93
+ ## Hooks and plugins
94
+
95
+ The initial lifecycle is `message.received`, `reply.decide`, `reply.compose`, `reply.before-send`, `reply.sent`, `memory.updated`, and `run.failed`. Hooks have a named/versioned owner-installed extension, deterministic registration order, a deadline, and a cancellation signal. Failure before dispatch closes admission. A notification-hook failure after a receipt cannot change that receipt or trigger resend.
96
+
97
+ Executable extensions are application code with the daemon's trust. They are installed outside contact folders; the agent cannot write them or turn message text into imports. Agent self-evolution means revising guidance and memory, not installing executable code. A future untrusted plugin mode needs its own process or language sandbox and explicit capabilities. In-process hooks are never described as a plugin security boundary.
98
+
99
+ The daemon reads a bounded private `plugins/extensions.json` manifest and preflights its complete inventory before importing listed TypeScript/JavaScript entry modules. Each default export must match the manifest ID/version and known hook names. Source digests appear in the loaded extension metadata. No directory scanning, package installation or hot reload occurs; changes require a full daemon process restart. The routed agent emits `memory.updated` only after a successful conditional write, with its path and committed revision. A notification failure does not undo that write or replay it.
100
+
101
+ ## Agentrouter
102
+
103
+ Agentrouter begins as an MIT-licensed source package independent of Textbutler's product model. Its account lease coordinates the selected provider account without embedding credentials in a contact workspace. Credential resolvers remain trusted host services. A lease cannot be stolen merely because its time elapsed while a process might still be alive.
104
+
105
+ Provider adapters declare observed, exact-version qualification. A launch plan is not proof of a sandbox. Codex's shell-disable setting and Claude's exact tool list are useful inputs, but an adapter is not admitted until attempted shell/process calls, host file reads, inherited MCP/plugin configuration, auth-file access, alternate agents, and additional workspaces are demonstrably blocked. No bypass-permissions mode is acceptable.
106
+
107
+ The source implements a pinned Claude Agent SDK adapter with explicit API-key account binding, a private verified executable snapshot, isolated runtime directories, no built-in tools or inherited settings, broker-only MCP, bounded raw output, and joined process-group termination. Its production gate requires independent qualification of the exact executable and SDK identity. Synthetic protocol and native-runtime fixtures do not activate it. The experimental Codex app-server driver verifies each model request's tool inventory through a host relay and keeps contact storage outside native scratch. It has no live account transport or production registration; native confinement and adversarial custody qualification remain incomplete. See the [Agentrouter implementation and evidence](../../packages/agentrouter/README.md).
108
+
109
+ A separately selected Claude API adapter executes the bounded tool loop in the trusted host. It exposes only the six broker tools and never starts a model-selected process. Account checks bind the actual packaged runtime and private credential generation; replacing a credential invalidates old discovery and running account work. Fresh model availability and price metadata select a cheap classifier. This explicit API choice does not silently replace Claude Code or Codex and does not use their subscription authentication.
110
+
111
+ The model receives a fixed contact/workspace identity and a fixed run ID. File operations are brokered and conditional. Public web requests are separately bounded and must not reach loopback, private networks, local sockets, or cloud metadata through DNS or redirects. Message tools stage recipient-free intents for the one conversation. Unknown tool names and unknown input fields fail. Credential/account services are never model tools.
112
+
113
+ Oompa's existing runtime provider port and account/process-custody patterns are source references. Its ordinary workspace-write execution profile is not the requested contact-only sandbox. AI Charts is a prospective second consumer. Do not migrate either product to Agentrouter until an adapter has equivalent feature and recovery evidence; avoid changing their active work in this redesign.
114
+
115
+ ## Ghostget contract and rich features
116
+
117
+ WhatsApp follows the same Ghostget ownership boundary. Its private wacli-backed transport provides durable observations and recipient-bound actions; pairing, session state and synchronization stay in Ghostget. The [WhatsApp guide](whatsapp.md) describes setup and action support. Textbutler does not embed WPPConnect or invoke wacli directly.
118
+
119
+ Ghostget owns its provider process and private control socket; Textbutler consumes only Ghostget's documented CLI and automation contracts.
120
+
121
+ The older generic messaging APIs retain expiring route references and owner-confirmed previews. Automation uses a separate explicit owner protocol with durable enrollment, revocable grants and event observations. The iMessage provider negotiates attachments, reactions, stickers, rich links and polls separately; unsupported App Clips and arbitrary experiences remain unavailable. Native Contacts directory discovery is not implemented in this protocol.
122
+
123
+ Text and file sending use the native helper's AppleScript path. The upstream rich-action bridge injects into Messages and requires SIP to be disabled. Textbutler and Ghostget never change that security setting or install the injection automatically. Rich actions therefore remain unavailable unless the required bridge is already working. This is a significant installation constraint, not a completed rich-messaging experience on a stock Mac.
124
+
125
+ Ghostget 0.18.2 includes the admitted imsg `0.14.1+private-transport.3` helper. Automated rich links use `send.rich` with `fetch_metadata: false`, constructing the URL and host-title card without helper metadata or image fetching. Availability still requires current managed permission and a compatible native bridge; this does not claim network isolation for Messages itself. The host never falls back to fetching an agent-provided URL.
126
+
127
+ The [Ghostget integration contract](ghostget-contract.md) documents the implemented binding, grant, event, action and receipt semantics.
128
+
129
+ The Textbutler transport supports capability negotiation, conversations, bounded history, cursor-based events, exact preparation, authorization and receipts. File paths become admitted bytes before crossing the boundary. Per-action permission and context checks stop a rich batch when conversation activity changes. Private database access stays inside Ghostget's provider implementation.
130
+
131
+ Linq's documented iMessage API includes attachments, reactions, stickers, rich links, App Clips, and experiences. App cards and rich links are standalone messages. Those hosted capabilities do not establish availability through native macOS Messages. Optional Linq would be a separate transport with explicit account setup, sender identity, webhook signature verification, replay protection, and the same Textbutler policy gates. It is not a way to silently route an owner's personal conversation through a different phone number.
132
+
133
+ Ghostget currently imports published Message Like Me bundle contracts. Keep that immutable package a leaf. Do not repoint it at the Textbutler runtime. Extract the neutral bundle contracts before reversing a live package dependency, or consume Ghostget's installed CLI contract without a package import in the interim. Preserve historical wire-format identifiers.
134
+
135
+ ## macOS menu companion
136
+
137
+ The supported local surface is an unbundled status-item companion launched by the CLI. It exposes daemon state, contact and account readiness, capabilities, recent activity, pause/resume and status refresh. The website action opens the informational textbutler.app page. Contact enrollment, account setup and other settings remain daemon protocol capabilities without a current menu or web interface. The companion is not required to run the daemon.
138
+
139
+ One narrow native command accepts the versioned control request. It connects to the private user socket, bounds requests/responses, applies timeouts, and verifies same-user ownership. The companion has no generic shell, filesystem, opener, or network plugin and does not inherit access to arbitrary Ghostget operations.
140
+
141
+ ## Admission still required
142
+
143
+ 1. Use the verified Ghostget 0.18.2 package, which includes the reviewed automation source and native helpers. Real account synchronization, recipient identity, rich actions and revocation still require a bounded owner-authorized live test; artifact admission and synthetic fixtures do not prove delivery.
144
+ 2. Independently qualify the native Claude SDK and Codex adapters for the requested no-shell, contact-only profile before enabling those choices. The separate Claude API path requires explicit account setup and packaged-runtime admission.
145
+ 3. Publish the CLI package with its pinned desktop-foundation SDK dependency. Verify the package bytes, the verified pinned runner download, singleton behavior, and the shared autostart install/uninstall lifecycle. A source checkout or missing companion must never trigger a build at launch.
146
+ 4. Keep historical repository and published package identities as compatibility and provenance anchors. The Textbutler site is assigned to `textbutler.app`; later identity migrations must preserve immutable artifacts and existing release protections.
147
+
148
+ ## Sources
149
+
150
+ - [Codex App Server](https://learn.chatgpt.com/docs/app-server): programmatic threads, turns, tool requests, account operations, and sandbox configuration.
151
+ - [Codex security](https://learn.chatgpt.com/docs/security): sandbox and approval boundaries.
152
+ - [Claude Agent SDK permissions](https://platform.claude.com/docs/en/agent-sdk/permissions): tool permission controls.
153
+ - [Linq messages](https://docs.linqapp.com/channel/imessage/api/resources/chats/subresources/messages/): transport-specific rich message behavior.
154
+ - [Linq reactions](https://docs.linqapp.com/channel/imessage/api/resources/messages/methods/add_reaction/): emoji and sticker reactions.
@@ -0,0 +1,88 @@
1
+ # Ghostget integration contract
2
+
3
+ Textbutler owns reply policy and contact memory. Ghostget owns messaging
4
+ accounts, native permissions, synchronization, event storage and outward
5
+ actions. Textbutler communicates with its own Ghostget owner process through
6
+ `ghostget messaging automation serve --stdio`; it does not share the Ghostget
7
+ menu companion's private helper or open provider databases.
8
+
9
+ [Ghostget 0.18.2](https://github.com/hraness/ghostget/releases/tag/v0.18.2) is the
10
+ verified published dependency for this contract. Its package includes both
11
+ private messaging runtimes; installation is explicit and starts no provider.
12
+ Older generic CLI routes do not become automation grants.
13
+
14
+ ## Owner process
15
+
16
+ The private protocol is `ghostget.messaging-automation/1`. Every bounded JSON
17
+ request has an ID, method and parameters; every response repeats its protocol
18
+ and ID and carries either a checked result or a typed error. Initialization
19
+ selects up to two explicit accounts, one per network, through stdin. Contact
20
+ memory and model tools cannot configure that process or invoke its control API.
21
+
22
+ Textbutler records process custody before launch, bounds its streams and queue,
23
+ and removes custody only after a successful close response and verified clean
24
+ process exit. Cancellation and revocation can interrupt an active submission.
25
+ A malformed response, crash or uncertain cleanup retains the recovery fence.
26
+ Restarting Textbutler does not silently clear it.
27
+
28
+ ## Contacts, events and grants
29
+
30
+ | Surface | Implemented contract |
31
+ | --- | --- |
32
+ | `status`, `start` | Observe capabilities; explicitly start supported synchronization. |
33
+ | `conversations`, `enroll`, `enrollments` | Exact account generation and direct participant-bound conversation enrollment. |
34
+ | `poll`, `history`, `events` | Bounded history, durable observation cursors, revisions, catch-up and gap detection. |
35
+ | `grant`, `grant.get`, `grant.by-intent`, `revoke` | Recipient, action, expiry and quota limits; idempotent issuance lookup and immediate revocation. |
36
+ | `asset`, `prepare` | Admit exact attachment bytes and bind the ordered action list to a context revision and expiry. |
37
+ | `submit`, `cancel`, `run` | Journal an action claim before dispatch and retain accepted, failed, partial or indeterminate results. |
38
+ | `close` | Wait for owned provider cleanup before acknowledging shutdown. |
39
+
40
+ Enrollment records contain the provider account incarnation, source generation,
41
+ implementation identity, exact conversation coordinate and participants. Display
42
+ titles are labels, not authority. Account or participant replacement invalidates
43
+ the binding. Existing Textbutler version 1 read bindings remain readable;
44
+ automation requires explicit version 2 enrollment.
45
+
46
+ Ghostget's managed permissions must explicitly allow each requested operation.
47
+ A broad capability advertisement does not create a grant. Textbutler activation
48
+ issues a bounded grant only after checking the selected agent account and
49
+ conversation. It renews standing enabled-contact grants within their limits,
50
+ and persists grant intent before issuance so a lost response can be resolved
51
+ without blindly creating another grant. Disabled and uncommitted grants are
52
+ reconciled through revocation.
53
+
54
+ Startup, re-enablement and recovery drain old events silently. An unresolved gap
55
+ pauses automation. Textbutler waits for a new eligible inbound event and applies
56
+ debounce, owner cooldown, classification and rate limits. Ghostget rechecks
57
+ identity, permission, context and grant before dispatch, and observes intervening
58
+ conversation changes between actions. Neither component retries an uncertain
59
+ send automatically.
60
+
61
+ ## Rich actions
62
+
63
+ Attachments, reactions, stickers, rich links and native polls are separate
64
+ capabilities. They become available only when the installed provider, current
65
+ account and managed permission admit them. Message targets must belong to the
66
+ enrolled conversation. Attachment and sticker paths are resolved by Textbutler's
67
+ contact file broker; Ghostget receives admitted bytes, not arbitrary paths.
68
+
69
+ Every response starts with disclosed text. For a nontext response, Textbutler
70
+ inserts a companion such as `🤖{ … }` before the rich actions. Execution stops
71
+ when a preceding action fails or the conversation changes. An accepted receipt
72
+ does not claim delivery.
73
+
74
+ Native iMessage exposes text and files through the pinned helper and its
75
+ currently usable rich methods for six standard tapbacks, stickers, links and
76
+ polls. Rich methods require the owner's separately configured native bridge.
77
+ App Clips and arbitrary mini-app experiences remain unavailable: no reviewed
78
+ native executor exists. Linq's hosted APIs are a design reference and cannot
79
+ silently substitute another sender for the owner's personal conversation.
80
+
81
+ ## Verification boundary
82
+
83
+ The source tests cover account changes, cursor restart and gaps, grant revocation
84
+ and lost responses, exact byte admission, ordered actions, cancellation and
85
+ uncertain results using synthetic accounts. They do not establish live provider
86
+ delivery. A bounded live acceptance test needs explicit account, recipient and
87
+ message authorization and must record the exact provider/runtime versions.
88
+ See [WhatsApp](whatsapp.md) and [the architecture](architecture.md).